
    /* ── Design tokens ───────────────────────────────────────────────── */

    :root {
      --bg:          #0a0c0f;
      --bg2:         #0f1218;
      --bg3:         #141820;
      --border:      #253040;
      --border2:     #334455;
      --gold:        #c8a96e;
      --gold2:       #e8c98e;
      --gold-dim:    #6b5a38;
      --edge:        #9a8a63;   /* interactive edges: hover, focus, active outline */
      --text:        #dde3ec;
      --text-dim:    #7e8fa0;
      --text-pale:   #8e9aaa;
      --red:         #e05c5c;
      --blue:        #5c9ee0;
      --green:       #5ce0a0;
      --pin-red:     #cc2200;
      --mono:        'JetBrains Mono', 'Cascadia Code', Consolas, monospace;
      --serif:       'Cormorant Garamond', Georgia, serif;
      --radius:      4px;
      --tab-h:       3rem;
      --header-h:    3.4rem;   /* slimmed: subtitle removed, so no two-line stack */
    }


    /* ── Reset ───────────────────────────────────────────────────────── */

    *, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; }

    body {
      background:  var(--bg);
      color:       var(--text);
      font-family: var(--mono);
      font-size:   1rem;
      line-height: 1.6;
      /* Pin to the viewport. Plain height:100vh lets iOS Safari scroll the page up
         to tuck its address bar away, dragging the banner + tab bar off-screen
         (out of reach). position:fixed locks the app so that can't happen. */
      position:    fixed;
      inset:       0;
      overflow:    hidden;
      display:     flex;
      flex-direction: column;
    }


    /* ── Header ──────────────────────────────────────────────────────── */

    .app-header {
      flex-shrink:   0;
      display:       flex;
      align-items:   center;
      gap:           1rem;
      padding:       0 1.5rem;
      height:        var(--header-h);
      border-bottom: 1px solid var(--border);
      background:    var(--bg);
    }

    /* Installed PWA: drop the branding banner — the OS launch splash + the
       About panel cover identity; don't waste standalone screen space on it.
       Media query covers modern browsers; .is-standalone (set in index.html)
       is the fallback for older iOS, which lacks display-mode. Zero --header-h
       too so the panel max-height calc (uses --header-h) reclaims the space. */
    @media all and (display-mode: standalone) {
      :root { --header-h: 0rem; }
      .app-header { display: none; }
    }
    .is-standalone { --header-h: 0rem; }      /* .is-standalone sits on <html> = :root */
    .is-standalone .app-header { display: none; }

    .app-header::before {
      content:   '\2609';
      font-size: 1.8rem;
      color:     var(--gold);
      animation: glow 4s ease-in-out infinite;
      flex-shrink: 0;
    }

    @keyframes glow {
      0%, 100% { text-shadow: 0 0 30px rgba(200,169,110,0.4); }
      50%       { text-shadow: 0 0 60px rgba(200,169,110,0.7); }
    }

    .app-title {
      font-family:    var(--serif);
      font-size:      1.5rem;
      font-weight:    300;
      letter-spacing: 0.12em;
      color:          var(--gold2);
      text-transform: uppercase;
    }



    /* ── Tab bar ─────────────────────────────────────────────────────── */

    .tab-bar {
      flex-shrink:   0;
      display:       flex;
      height:        var(--tab-h);
      border-bottom: 1px solid var(--border2);
      background:    #070911;
    }

    .tab-btn {
      flex:           1;
      background:     transparent;
      border:         none;
      border-bottom:  2px solid transparent;
      color:          var(--text-dim);
      font-family:    var(--mono);
      font-size:      0.72rem;
      letter-spacing: 0.12em;
      text-transform: uppercase;
      cursor:         pointer;
      transition:     all 0.15s;
      padding:        0 0.5rem;
    }
    /* Hamburger as an inline SVG, not the ☰ glyph: the glyph renders hairline-thin on
       iOS and fuses into a block when stroked. Solid rounded bars read cleanly at any
       size and match the visual weight of the colour-emoji tab icons. */
    .tab-icon-svg {
      width: 1.15em;
      height: 1.15em;
      vertical-align: -0.2em;
      /* currentColor: every tab icon is SVG now, so the icon simply takes the
         button's colour and the active state is declared once, on the button. */
      fill: currentColor;
      filter: drop-shadow(0 1px 1px rgba(0,0,0,0.35));
    }

    .tab-btn:hover    { color: var(--text); }
    .tab-btn.active   {
      color:        var(--gold);
      border-bottom-color: var(--gold);
      background:   var(--bg);
    }


    /* ── Tab content area ────────────────────────────────────────────── */

    .tab-content {
      flex:     1;
      overflow: hidden;
      position: relative;
    }

    .tab-panel {
      position:  absolute;
      inset:     0;
      overflow-y: auto;
      padding:   1.25rem 1.5rem 2rem;
      display:   none;
    }
    .tab-panel.active { display: block; }

    /* Constrain content width on wide screens */
    .tab-panel > * {
      max-width: 860px;
      margin-left: auto;
      margin-right: auto;
    }

    /* Map panel has no padding — map fills edge to edge */
    #tab-map {
      padding:  0;
      overflow: hidden;  /* map fills the panel; no outer scroll */
    }
    #tab-map > * {
      max-width: none;
    }
    #map {
      touch-action: pan-x pan-y;  /* allow map panning but not page scroll */
    }


    /* ── Common panel/section styles ─────────────────────────────────── */

    .panel {
      background:    var(--bg2);
      border:        1px solid var(--border);
      border-radius: var(--radius);
      padding:       1.25rem;
      margin-bottom: 1.25rem;
    }

    .section-label {
      font-family:    var(--mono);
      font-size:      0.68rem;
      letter-spacing: 0.2em;
      text-transform: uppercase;
      color:          var(--text-dim);
      margin-bottom:  0.65rem;
    }

    /* Top-level section headers — used for Global Circumstances and
       Local Circumstances. Stronger than .section-label / .subsection-label. */
    .top-section-header {
      font-family:    var(--mono);
      font-size:      0.92rem;
      font-weight:    700;
      letter-spacing: 0.18em;
      text-transform: uppercase;
      color:          var(--gold);
      margin-bottom:  0.75rem;
      padding-bottom: 0.5rem;
      border-bottom:  1px solid var(--border);
    }

    /* Inline coords appended to a top-section-header — normal case, smaller,
       so the location reads at a glance without claiming its own row.
       On narrow screens it wraps to its own line below the heading. */
    .header-coords {
      margin-left:    0.75rem;
      font-size:      0.7rem;
      letter-spacing: 0.04em;
      text-transform: none;
      color:          var(--text-dim);
    }

    @media (max-width: 480px) {
      .header-coords {
        display:     block;
        margin-left: 0;
        margin-top:  0.2rem;
      }
    }


    /* ── Form controls ───────────────────────────────────────────────── */

    .field { margin-bottom: 1rem; }
    .field:last-child { margin-bottom: 0; }

    label {
      display:        block;
      font-family:    var(--mono);
      font-size:      0.68rem;
      letter-spacing: 0.1em;
      text-transform: uppercase;
      color:          var(--text-dim);
      margin-bottom:  0.35rem;
    }

    /* Form controls — applies to select + textarea. The only remaining inputs
       are type=range sliders, which have their own block below and override
       most of these properties anyway. */
    select, textarea {
      width:         100%;
      background:    var(--bg3);
      border:        1px solid var(--border2);
      border-radius: var(--radius);
      color:         var(--text);
      font-family:   var(--mono);
      font-size:     0.88rem;
      padding:       0.5rem 0.7rem;
      outline:       none;
      transition:    border-color 0.2s;
      appearance:    none;
    }
    select:focus, textarea:focus { border-color: var(--edge); }

    select {
      background-image:    url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='10' height='6'%3E%3Cpath d='M0 0l5 6 5-6z' fill='%236b7788'/%3E%3C/svg%3E");
      background-repeat:   no-repeat;
      background-position: right 0.75rem center;
      padding-right:       2rem;
      cursor:              pointer;
    }


    /* ── Buttons ─────────────────────────────────────────────────────── */

    .btn {
      font-family:    var(--mono);
      font-size:      0.75rem;
      letter-spacing: 0.1em;
      text-transform: uppercase;
      padding:        0.55rem 1.2rem;
      border-radius:  var(--radius);
      border:         none;
      cursor:         pointer;
      transition:     all 0.2s;
      white-space:    nowrap;
    }
    .btn-primary {
      background:  var(--gold);
      color:       var(--bg);
      font-weight: 700;
    }
    .btn-primary:hover    { background: var(--gold2); box-shadow: 0 0 20px rgba(200,169,110,0.25); }
    .btn-primary:disabled { background: var(--gold-dim); color: var(--bg2); cursor: not-allowed; box-shadow: none; }

    .btn-ghost {
      background: transparent;
      color:      var(--text-dim);
      border:     1px solid var(--border2);
    }
    .btn-ghost:hover { border-color: var(--edge); color: var(--text); }

    .btn-danger {
      background: transparent;
      color:      var(--red);
      border:     1px solid rgba(224,92,92,0.3);
    }
    .btn-danger:hover { background: rgba(224,92,92,0.1); border-color: var(--red); }


    /* ── Search tab ──────────────────────────────────────────────────── */

    .search-box {
      position:      relative;
      margin-bottom: 0.75rem;
    }
    .search-box textarea {
      box-sizing:    border-box;
      field-sizing:  content;          /* native auto-height on supporting browsers */
      padding-right: 2.5rem;
      font-size:     0.75rem;
      resize:        none;
      overflow:      hidden;
    }
    .search-clear {
      position:   absolute;
      right:      0.5rem;
      top:        0.5rem;
      background: none;
      border:     none;
      color:      var(--text-dim);
      cursor:     pointer;
      font-size:  1rem;
      padding:    0.25rem;
      line-height: 1;
    }
    .search-clear:hover { color: var(--text); }

    .search-hint {
      font-family: var(--mono);
      font-size:   0.65rem;
      color:       var(--text-dim);
      margin-bottom: 0.75rem;
      line-height: 1.6;
    }
    .search-hint strong { color: var(--text-pale); font-weight: 400; }

    .pill-row {
      display:       flex;
      gap:           0.4rem;
      flex-wrap:     wrap;
      margin-bottom: 0.75rem;
    }

    .pill {
      font-family:    var(--mono);
      font-size:      0.68rem;
      letter-spacing: 0.06em;
      padding:        0.35rem 0.75rem;  /* taller for touch */
      border-radius:  20px;
      border:         1px solid var(--border2);
      background:     transparent;
      color:          var(--text-dim);
      cursor:         pointer;
      transition:     all 0.15s;
      min-height:     2rem;             /* ~32px minimum touch target */
    }
    .pill:hover  { border-color: var(--edge); color: var(--text); }
    .pill.active { background: rgba(200,169,110,0.12); border-color: var(--edge); color: var(--gold); }
    .pill.pill-loc { background: rgba(200,169,110,0.1); border-color: var(--edge); color: var(--gold); }

    /* Obscuration slider */
    .osc-row {
      display:     flex;
      align-items: center;
      gap:         0.75rem;
      margin-bottom: 0.75rem;
    }
    .osc-label {
      font-family: var(--mono);
      font-size:   0.68rem;
      color:       var(--text-dim);
      white-space: nowrap;
      min-width:   7rem;
    }
    .osc-value {
      font-family: var(--mono);
      font-size:   0.72rem;
      color:       var(--gold2);
      min-width:   3rem;
      text-align:  right;
    }
    input[type=range] {
      flex:       1;
      padding:    0;
      background: transparent;
      border:     none;
      accent-color: var(--gold);
    }

    /* Scan progress */
    .scan-bar {
      display:     flex;
      gap:         0.6rem;
      align-items: center;
      margin-bottom: 0.75rem;
      flex-wrap:   wrap;
    }
    .scan-progress {
      font-family: var(--mono);
      font-size:   0.7rem;
      color:       var(--text-dim);
      flex:        1;
      min-width:   8rem;
    }
    .progress-track {
      height:        3px;
      background:    var(--border2);
      border-radius: 2px;
      margin-top:    0.3rem;
      overflow:      hidden;
    }
    .progress-fill {
      height:        100%;
      background:    var(--gold-dim);
      border-radius: 2px;
      transition:    width 0.15s;
      width:         0%;
    }

    /* Eclipse list */
    .eclipse-list {
      border:        1px solid var(--border);
      border-radius: 0 0 var(--radius) var(--radius);
      background:    var(--bg3);
      /* Height fills remaining space in tab panel */
      max-height:    calc(100vh - var(--header-h) - var(--tab-h) - 16rem);
      min-height:    8rem;
      overflow-y:    auto;
    }
    .eclipse-list::-webkit-scrollbar       { width: 5px; }
    .eclipse-list::-webkit-scrollbar-thumb { background: var(--border2); border-radius: 3px; }

    .eclipse-item {
      display:               grid;
      grid-template-columns: 2rem 1fr 4.5rem;
      align-items:           center;
      gap:                   0.5rem;
      padding:               0.65rem 0.75rem;
      cursor:                pointer;
      border-bottom:         1px solid var(--border);
      font-family:           var(--mono);
      font-size:             0.88rem;
      transition:            background 0.1s;
    }
    .eclipse-item:last-child { border-bottom: none; }
    .eclipse-item:hover      { background: rgba(255,255,255,0.03); }
    .eclipse-item.selected   { background: rgba(200,169,110,0.15); border-left: 3px solid var(--gold); color: var(--gold); }

    .et-T { color: var(--gold2); }
    .et-A { color: var(--blue); }
    .et-H { color: var(--green); }
    .et-P { color: var(--text-dim); }

    .list-status {
      padding:     1rem;
      font-family: var(--mono);
      font-size:   0.76rem;
      color:       var(--text-dim);
      text-align:  center;
    }

    /* Selected eclipse bar */
    /* ── Data tab ────────────────────────────────────────────────────── */

    .no-selection {
      text-align:  center;
      padding:     3rem 1rem;
      color:       var(--text-dim);
      font-style:  italic;
      font-size:   1.05rem;
    }

    .result-header {
      margin-bottom:   1.25rem;
      padding-bottom:  1rem;
      border-bottom:   1px solid var(--border);
    }
    .result-title {
      font-size:   1.5rem;
      font-weight: 300;
      font-style:  italic;
      color:       var(--text);
      margin-bottom: 0.2rem;
    }
    .result-title .type-badge {
      font-size:     0.72rem;
      vertical-align: middle;
      position:      relative;
      top:           -0.15rem;
    }
    .type-badge {
      font-family:    var(--mono);
      font-size:      0.7rem;
      letter-spacing: 0.12em;
      text-transform: uppercase;
      padding:        0.25rem 0.75rem;
      border-radius:  20px;
      white-space:    nowrap;
    }
    .badge-total   { background: rgba(200,169,110,0.15); color: var(--gold2);     border: 1px solid var(--gold-dim); }
    .badge-annular { background: rgba(92,158,224,0.15);  color: var(--blue);      border: 1px solid #3a6e9e; }
    .badge-partial { background: rgba(110,119,136,0.15); color: var(--text-pale); border: 1px solid var(--border2); }
    .badge-hybrid  { background: rgba(92,224,160,0.15);  color: var(--green);     border: 1px solid #3a9e70; }

    /* ── Details panel ──────────────────────────────────────────────────
       One type scale, one spacing scale, two columns aligned by a real
       table. Section header, data table, subsection header, contacts
       table — same rhythm throughout. */

    /* Both tiers are GOLD. The hierarchy is carried by SIZE alone: the top tier
       is a clear notch larger and bold, the sub tier stays at its original
       small size. Colour is not doing hierarchy work here, so it is free to
       mark these as headings against the grey of labels and body text. */
    .detail-section-h {
      font-family:    var(--mono);
      font-size:      0.92rem;
      font-weight:    700;
      letter-spacing: 0.15em;
      text-transform: uppercase;
      color:          var(--gold);
      margin:         1.6rem 0 0.5rem;
      padding:        0;
    }
    .detail-section-h:first-child { margin-top: 0; }

    /* SUB level (Contact Times, Sun Track): gold too, at the original small
       size. Against .detail-table .l — same size bracket, but grey and not
       bold — the gold is what says "heading". */
    .detail-sub-h {
      font-family:    var(--mono);
      font-size:      0.72rem;
      font-weight:    700;
      letter-spacing: 0.15em;
      text-transform: uppercase;
      color:          var(--gold);
      margin:         1.5rem 0 0.4rem;
      padding:        0;
      border:         none;
    }

    .detail-section-h .header-coords {
      margin-left:    0.6rem;
      font-size:      0.66rem;
      letter-spacing: 0.04em;
      text-transform: none;
      color:          var(--text-dim);
    }

    /* Data table: label column left, value column right. Real table so
       column edges align across all rows. */
    .detail-table {
      width:           100%;
      border-collapse: collapse;
      font-family:     var(--mono);
      table-layout:    fixed;
    }
    .detail-table td {
      /* Softer than --border and roomier than before. Nine rows of Global
         Circumstances at full border weight read as a ladder; the eye wants the
         rows grouped, not fenced. Enough contrast to guide a horizontal read,
         not enough to draw attention. */
      padding:        0.3rem 0;
      border-bottom:  1px solid var(--border);
      vertical-align: baseline;
    }
    .detail-table tr:last-child td { border-bottom: none; }
    .detail-table .l {
      width:          11rem;
      font-size:      0.66rem;
      letter-spacing: 0.08em;
      text-transform: uppercase;
      color:          var(--text-dim);
      white-space:    nowrap;
    }
    .detail-table .v {
      font-size:   0.82rem;
      color:       var(--gold2);
      text-align:  right;
      overflow:    hidden;
      text-overflow: ellipsis;
      white-space: nowrap;
    }

    /* Sits directly under "Local Circumstances" and names the place the figures
       below are for. No negative margin any more: the heading's own
       margin-bottom is the gap, so this can't drift when that changes. */
    .detail-subloc {
      font-family:    var(--mono);
      font-size:      0.7rem;
      color:          var(--text-dim);
      margin:         0 0 0.8rem;
      padding:        0;
    }

    /* Contact-times table — single time column (UT), header band, row stripes. */
    .contacts-table {
      width:           100%;
      border-collapse: collapse;
      font-family:     var(--mono);
      font-size:       0.8rem;
      table-layout:    auto;
    }
    .contacts-table th,
    .contacts-table td {
      padding:        0.16rem 0.7rem 0.16rem 0;
      vertical-align: middle;
      white-space:    nowrap;
    }
    .contacts-table th:last-child,
    .contacts-table td:last-child { padding-right: 0.4rem; }
    .contacts-table th {
      text-align:     left;
      font-size:      0.66rem;
      letter-spacing: 0.1em;
      text-transform: uppercase;
      color:          var(--text-dim);
      font-weight:    400;
      padding-bottom: 0.6rem;
      border-bottom:  1px solid var(--border);
    }
    .contacts-table .day-off {
      font-size:  0.65em;
      color:      var(--text-dim);
      margin-left: 0.15rem;
      vertical-align: super;
      line-height: 1;
    }
    .contacts-table th:first-child { padding-left: 0.55rem; }
    .contacts-table th.time-mode-toggle {
      cursor:           pointer;
      color:            var(--text);
      text-decoration:  underline dotted;
      text-underline-offset: 3px;
    }
    .contacts-table th.time-mode-toggle:hover { color: var(--text); }
    .contacts-table th,
    .contacts-table td             { padding-left: 0.55rem; }
    .contacts-table td             { color: var(--text); }
    .contacts-table tbody tr:nth-child(odd) td { background: rgba(255,255,255,0.025); }
    .contacts-table td svg         { vertical-align: middle; margin-right: 0.4rem; }
    .contacts-table .row-umbral td { color: var(--gold2); }
    .contacts-table .row-max    td { color: var(--gold2); font-weight: 700; }

    /* Footnote under the contacts table: small, dim, mono. */
    .contacts-note {
      font-family: var(--mono);
      font-size:   0.66rem;
      color:       var(--text-dim);
      margin:      0.4rem 0 0;
      padding:     0;
    }

    /* Empty / loading states */
    .no-location, .no-eclipse {
      text-align: center;
      padding:    1.5rem 0.5rem;
      color:      var(--text-dim);
      font-family: var(--mono);
      font-size:  0.82rem;
    }

    .note {
      margin-top:  0.6rem;
      font-family: var(--mono);
      font-size:   0.66rem;
      color:       var(--text-dim);
      padding:     0.45rem 0.7rem;
      border-left: 2px solid var(--border);
      line-height: 1.6;
    }

    /* Status message */
    #status-msg {
      font-family: var(--mono);
      font-size:   0.75rem;
      min-height:  1.2rem;
      margin-top:  0.5rem;
      color:       var(--text-dim);
    }
    #status-msg.err { color: var(--red); }


    /* ── Settings tab ────────────────────────────────────────────────── */

    .settings-section { margin-bottom: 2rem; }

    /* Collapsible settings groups */
    .settings-group {
      border:        1px solid var(--border);
      border-radius: var(--radius);
      margin-bottom: 1rem;
      overflow:      hidden;
    }
    .settings-group-header {
      display:        flex;
      align-items:    center;
      font-family:    var(--mono);
      font-size:      0.8rem;
      letter-spacing: 0.14em;
      text-transform: uppercase;
      color:          var(--text);
      padding:        0.75rem 1rem;
      background:     var(--bg2);
      cursor:         pointer;
      user-select:    none;
      list-style:     none;
    }
    .settings-group-header::-webkit-details-marker { display: none; }
    .settings-group-header::after {
      content:    '▾';
      margin-left: auto;
      font-size:  0.7rem;
      color:      var(--text-dim);
      transition: transform 0.2s;
    }
    .settings-group:not([open]) .settings-group-header::after {
      transform: rotate(-90deg);
    }
    .settings-group > :not(summary) {
      padding: 0.75rem 1rem 0;
    }

    .settings-row {
      display:     flex;
      align-items: center;
      gap:         1rem;
      padding:     0.6rem 0;
      border-bottom: 1px solid var(--border);
      font-family: var(--mono);
      font-size:   0.72rem;
    }
    .settings-row:last-child { border-bottom: none; }
    .settings-row label {
      flex:          0 0 auto;
      font-size:     0.72rem;
      text-transform: none;
      letter-spacing: 0;
      color:         var(--text);
      margin:        0;
      white-space:   nowrap;
    }
    .settings-row select {
      width: auto;
      min-width: 12rem;
      max-width: 22rem;
      font-size: 0.72rem;
      font-family: var(--mono);
    }

    .about-text {
      font-family: var(--mono);
      font-size:   0.85rem;
      line-height: 1.6;
      color:       var(--text);
    }
    /* Body text here is --text-dim (#7e8fa0) — every .about-text sits inside a
       .settings-group, and that rule dims it (see below). Emphasis is a SMALL
       step up from that: #96a4b3, a quarter of the way toward --text, 1.3x
       luminance. Still plainly grey.
       Weight does the real work: 700 was invisible because JetBrains Mono's
       bold is a restrained coding weight, so this is 800 (ExtraBold).
       To tune, move along the --text-dim -> --text ramp: #8c9cab is subtler,
       #9facbb is stronger. --text itself (#dde3ec) reads as white here.
       NOT gold — gold is overloaded in this palette. */
    .about-text strong {
      font-weight: 800;
      color:       #96a4b3;
    }
    .settings-group .about-text { font-size: 0.72rem; color: var(--text-dim); }
    /* Icons quoted inside running prose (see the log block in Instructions).
       These use TIGHT per-icon viewBoxes, unlike the app's buttons which keep
       0 0 20 20 — there the box is the tap target, here it is dead space, and
       each glyph fills a different fraction of it (the arrow only 38% against
       the flag's 69%), so a shared box made them look mismatched.
       Height is set in em so they track the paragraph; vertical-align centres
       the box on the x-height rather than the baseline, which is what makes an
       inline icon sit level with lowercase text instead of riding high. */
    .inline-icon {
      display:        inline-block;
      width:          1.5em;
      height:         1.5em;
      vertical-align: -0.5em;
      color:          var(--text);
    }
    /* The star is a text glyph, not an SVG, so it needs neither. */
    span.inline-icon {
      width:          auto;
      height:         auto;
      vertical-align: baseline;
      font-size:      1.35em;
      line-height:    1;
    }

    .about-text p  { margin-bottom: 0.75rem; }
    .about-text a  { color: var(--text); text-decoration: underline;
                     text-underline-offset: 0.18em; }
    .about-text a:hover { color: var(--text-pale); }

    .syntax-dl {
      margin:      0.5rem 0 0;
      padding:     0;
    }
    .syntax-entry {
      display:     grid;
      grid-template-columns: 5.5rem 1fr;
      gap:         0 0.75rem;
      padding:     0.35rem 0;
      align-items: baseline;
    }
    .syntax-entry dt {
      font-family:    var(--mono);
      font-size:      0.68rem;
      letter-spacing: 0.08em;
      text-transform: uppercase;
      color:          var(--text);
      font-weight:    700;
      padding-top:    0.1rem;
      align-self:     start;
    }
    .syntax-entry dd {
      margin:      0;
      font-family: var(--mono);
      font-size:   0.73rem;
      color:       var(--text);
      line-height: 1.65;
    }


    /* Tolerances table (reuses syntax-table class) */
    .syntax-table {
      width:           100%;
      border-collapse: collapse;
      font-family:     var(--mono);
      font-size:       0.72rem;
      margin:          0.75rem 0;
    }
    .syntax-table th {
      text-align:     left;
      color:          var(--text-dim);
      font-size:      0.65rem;
      letter-spacing: 0.1em;
      text-transform: uppercase;
      padding:        0.4rem 0.5rem;
      border-bottom:  1px solid var(--border2);
      font-weight:    400;
    }
    .syntax-table td {
      padding:        0.4rem 0.5rem;
      border-bottom:  1px solid var(--border);
      vertical-align: top;
    }
    .syntax-table tr:last-child td { border-bottom: none; }
    .syntax-table td:first-child   { color: var(--gold2); white-space: nowrap; }
    .syntax-table td:last-child    { color: var(--text-pale); }



    .detail-title {
      display:       flex;
      align-items:   center;
      gap:           0.6rem;
      font-family:   var(--mono);
      font-size:     1.1rem;
      color:         var(--text);
      padding:       0.25rem 0 0.5rem;
      border-bottom: 1px solid var(--border);
      margin-bottom: 0.5rem;
      position:      sticky;
      top:           -1.25rem;        /* cancel the tab-panel top padding */
      background:    var(--bg);
      z-index:       5;
      margin-top:    -1.25rem;         /* pull up into the padding so it pins flush */
      padding-top:   1.25rem;          /* restore visual top spacing */
    }
    /* In the sidebar the panel background is lighter than --bg; match it so the
       pinned header blends with the sheet it sits on. */
    .sidebar .detail-title { background: #1a2030; }

    /* The date takes the slack and ellipsises if it must; the actions never
       shrink. Without min-width:0 a flex item refuses to go below its content
       width, so a long date pushed the buttons onto a second line instead. */
    .detail-title-date {
      flex:          1 1 auto;
      min-width:     0;
      overflow:      hidden;
      text-overflow: ellipsis;
      white-space:   nowrap;
    }
    .detail-title-icon {
      display:     inline-flex;
      align-items: center;
      flex:        0 0 auto;
    }

    /* Title actions: icon-only, square, grouped. They used to carry the words
       "Share" and "Save"; two labelled buttons plus a long date (e.g.
       "12 August 2026") ran past the sidebar width and wrapped the date onto a
       second line. The glyphs plus title/aria-label carry the same meaning in a
       fifth of the space. */
    .detail-actions {
      margin-left: auto;
      display:     flex;
      gap:         0.3rem;
      flex:        0 0 auto;
    }
    .icon-btn {
      display:         flex;
      align-items:     center;
      justify-content: center;
      width:           1.75rem;
      height:          1.75rem;
      padding:         0;
      font-size:       0.9rem;
      line-height:     1;
      cursor:          pointer;
      background:      transparent;
      border:          1px solid var(--border);
      border-radius:   var(--radius);
      color:           var(--text-dim);
      font-family:     var(--mono);
    }
    .icon-btn:hover { border-color: var(--edge); color: var(--text); }
    .icon-btn svg { display: block; width: 0.95rem; height: 0.95rem; }
    /* The star is SVG, not a text glyph: a glyph centres on its LINE BOX
       (which includes ascender and descender space), so it sat visibly high in
       a flex-centred square no matter what line-height was set. An SVG centres
       on its own box. */
    .log-save-btn svg { width: 1.1rem; height: 1.1rem; }

    /* ── User log panel ─────────────────────────────────── */

    /* The saved state of the star button in the detail title (.icon-btn, above,
       with the rest of the title). */
    .log-save-btn.saved { color: var(--gold); border-color: var(--edge); }

    .log-summary {
      padding:        0.6rem 0.75rem;
      font-size:      0.72rem;
      letter-spacing: 0.08em;
      text-transform: uppercase;
      color:          var(--text-dim);
      border-bottom:  1px solid var(--border);
    }

    .log-empty { padding: 1.5rem 1rem; color: var(--text-dim); font-size: 0.85rem; }
    .log-empty-hint { margin-top: 0.6rem; color: var(--text-dim); line-height: 1.7; }

    .log-item { border-bottom: 1px solid var(--border); }
    .log-item:last-child { border-bottom: none; }
    .log-item.selected .log-date { color: var(--gold); }

    /* Two grid rows. The actions sit on the DATE line, so the coordinates get
       the full width of the row underneath instead of being squeezed into
       whatever the icons leave over — which is what was truncating them.
       The type icon spans both rows and stays at the top.
       The LEFT of the type icon is reserved for the coming t-shirt selection
       checkboxes — do not put anything there. */
    .log-row {
      display:               grid;
      grid-template-columns: auto auto 1fr auto;
      grid-template-areas:   "pick ico date acts"
                             "pick ico loc  loc";
      align-items:           center;
      column-gap:            0.5rem;
      row-gap:               0.1rem;
      padding:               0.55rem 0.6rem;
    }
    .log-row:hover { background: rgba(255,255,255,0.03); }

    .log-ico  { grid-area: ico; display: flex; align-self: start; margin-top: 0.15rem; }
    .log-date { grid-area: date; font-size: 0.88rem; color: var(--text); }

    /* Tight cluster — these three belong together, so they read as one control
       group rather than three scattered buttons. */
    .log-acts {
      grid-area: acts;
      display:   flex;
      gap:       0.05rem;
    }

    .log-loc {
      grid-area:   loc;
      display:     flex;
      align-items: center;
      gap:         0.2rem;
      min-width:   0;
    }
    .log-coords {
      font-size:     0.7rem;
      color:         var(--text-dim);
      overflow:      hidden;
      text-overflow: ellipsis;
      white-space:   nowrap;
    }
    .log-coords.none { font-style: italic; opacity: 0.75; }
    /* What the eclipse does at that spot — a DATA value, so gold2. */
    .log-circ {
      font-size:   0.7rem;
      color:       var(--gold2);
      white-space: nowrap;
      flex:        0 0 auto;
      margin-left: auto;      /* right-justified against the row edge */
    }

    /* Every row action shares one footprint so the icon columns line up. */
    .log-act {
      flex:            0 0 auto;
      display:         flex;
      align-items:     center;
      justify-content: center;
      width:           1.6rem;
      height:          1.6rem;
      padding:         0;
      background:      transparent;
      border:          1px solid transparent;
      border-radius:   var(--radius);
      cursor:          pointer;
      color:           var(--text-dim);
    }
    .log-act svg          { width: 1rem; height: 1rem; display: block; }
    .log-act:hover        { border-color: var(--edge); color: var(--text); }
    .log-act:disabled     { opacity: 0.3; cursor: default; }
    .log-act:disabled:hover { border-color: transparent; color: var(--text-dim); }

    /* The pencil is inline with the coordinates it edits, so it's smaller and
       unbordered — it belongs to that line, not to the row's action cluster. */
    .log-edit-btn        { width: 1.35rem; height: 1.35rem; }
    .log-edit-btn svg    { width: 0.8rem; height: 0.8rem; }

    /* Seen is a flag that LIGHTS: hollow when unseen, filled gold when seen.
       No checkbox — a checkbox here would collide with the t-shirt selection
       control that is coming, and read as the same kind of thing. */
    .log-flag.on         { color: var(--gold); }
    .log-del:hover       { border-color: var(--red); color: var(--red); }
    .log-goto:hover      { color: var(--text); }

    /* Grid, not wrapped flex: four equal icon buttons on one row. It was five
       text buttons in a 2-column grid with "Make map" spanning both — wrapped
       flex before that, which left Import stranded alone because wrapping
       depends on text width rather than on any intent. Grid keeps the intent
       explicit at any label length, which is why it stays now the labels are
       gone. */
    .log-tools {
      display:               grid;
      grid-template-columns: repeat(4, 1fr);
      gap:                   0.4rem;
      padding:               0.85rem 0.75rem;
      border-top:            1px solid var(--border);
    }
    /* The base rule. It was lost in a rewrite of this block, which left every
       button in the panel as a bare <button> — browser-default grey-on-silver.
       If these ever look like OS buttons again, this rule has gone missing. */
    .log-btn {
      font-family:    var(--mono);
      font-size:      0.72rem;
      letter-spacing: 0.06em;
      padding:        0.4rem 0.5rem;
      cursor:         pointer;
      background:     transparent;
      border:         1px solid var(--border);
      border-radius:  var(--radius);
      color:          var(--text-dim);
      white-space:    nowrap;
      text-align:     center;
    }
    .log-btn:hover { border-color: var(--edge); color: var(--text); }
    /* Icon-only now, so the button is a box for one 20px mark rather than a run
       of text: centre it and let the padding set the hit area, which stays
       comfortably past the 44px touch minimum at this padding. */
    .log-btn svg {
      width:   20px;
      height:  20px;
      display: block;
      margin:  0 auto;
    }


    /* ── Sheet ────────────────────────────────────────────────────────
       A reusable overlay. The three map overlays are queued to use this same
       pattern, so dismissal, backdrop and z-index are settled once here.
       Slides up on mobile, centres on desktop. */
    .sheet {
      position: fixed;
      inset:    0;
      z-index:  1100;
      display:  none;
    }
    .sheet.open { display: block; }
    .sheet-backdrop {
      position:   absolute;
      inset:      0;
      background: rgba(0,0,0,0.72);
    }
    .sheet-body {
      position:       absolute;
      left:           0;
      right:          0;
      bottom:         0;
      /* DEFINITE height, not just a max. .sheet-content is flex:1 1 0, so it
         contributes nothing to intrinsic height — with max-height alone the
         whole sheet collapses to head + foot and the map disappears. The two
         must be changed together. */
      height:         88vh;
      max-height:     92vh;
      display:        flex;
      flex-direction: column;
      background:     var(--bg2);
      border-top:     1px solid var(--border2);
      border-radius:  var(--radius) var(--radius) 0 0;
      transform:      translateY(100%);
      transition:     transform 0.22s ease;
    }
    .sheet.open .sheet-body { transform: translateY(0); }

    .sheet-head, .sheet-foot {
      display:     flex;
      align-items: center;
      gap:         0.5rem;
      padding:     0.7rem 0.85rem;
      flex:        0 0 auto;
    }
    .sheet-head { border-bottom: 1px solid var(--border); }
    .sheet-foot { border-top:    1px solid var(--border); flex-wrap: wrap; }
    .sheet-title {
      font-family:    var(--mono);
      font-size:      0.8rem;
      font-weight:    700;
      letter-spacing: 0.15em;
      text-transform: uppercase;
      color:          var(--gold);
    }
    .sheet-sub {
      font-family: var(--mono);
      font-size:   0.7rem;
      color:       var(--text-dim);
    }
    /* A proper tap target. At 1.3rem of glyph with 0.2rem of padding this was
       well under the 44px minimum, and on a phone it is the main way out — the
       backdrop is only the top ~12% of the screen. */
    /* A LABELLED button, not a bare glyph. A lone × in the corner of a sheet
       was reported as "no indication of how to escape" — the word is what makes
       it read as a way out. Full 44px tap target, bordered so it reads as a
       control rather than decoration. */
    .sheet-close {
      margin-left:     auto;
      display:         flex;
      align-items:     center;
      justify-content: center;
      gap:             0.1rem;
      min-height:      2.4rem;
      padding:         0 0.7rem;
      background:      transparent;
      border:          1px solid var(--edge);
      border-radius:   var(--radius);
      color:           var(--text);
      font-family:     var(--mono);
      font-size:       0.78rem;
      letter-spacing:  0.06em;
      line-height:     1;
      cursor:          pointer;
      flex:            0 0 auto;
    }
    .sheet-close:hover { color: var(--text-pale); }

    /* Grab handle: the standard signal that a sheet can be pulled down. Mobile
       only — on desktop this is a centred dialog with Escape and a backdrop. */
    .sheet-body::before {
      content:       '';
      display:       block;
      width:         2.5rem;
      height:        0.25rem;
      margin:        0.45rem auto 0;
      border-radius: 999px;
      background:    var(--border2);
      flex:          0 0 auto;
    }
    @media (min-width: 900px) {
      .sheet-body::before { display: none; }
    }

    .sheet-content {
      flex:            1 1 0;
      overflow:        hidden;   /* the map is scaled to fit, never scrolled */
      padding:         0.85rem;
      min-height:      0;        /* lets a flex child actually shrink */
      display:         flex;
      align-items:     center;
      justify-content: center;
    }
    /* Fit the poster INSIDE the sheet in both axes. width:100% alone overflows
       any projection taller than it is wide, which is why the maps ran off the
       bottom; max-height plus the SVG's own preserveAspectRatio letterboxes it
       instead. */
    .sheet-content svg {
      display:    block;
      width:      auto;
      height:     auto;
      max-width:  100%;
      max-height: 100%;
    }
    .sheet-foot select { width: auto; min-width: 9rem; font-size: 0.72rem; }
    .ts-wait {
      font-family: var(--mono);
      font-size:   0.78rem;
      color:       var(--text-dim);
      text-align:  center;
      padding:     2rem 1rem;
      line-height: 1.7;
    }

    @media (min-width: 900px) {
      /* Centred dialog on desktop — a bottom sheet on a wide screen wastes the
         width the poster actually needs. */
      .sheet-body {
        top:            50%;
        left:           50%;
        right:          auto;
        bottom:         auto;
        width:          min(980px, 92vw);
        height:         86vh;
        max-height:     86vh;
        border:         1px solid var(--border2);
        border-radius:  var(--radius);
        transform:      translate(-50%, -46%);
        opacity:        0;
        transition:     transform 0.18s ease, opacity 0.18s ease;
      }
      .sheet.open .sheet-body { transform: translate(-50%, -50%); opacity: 1; }
    }

    /* The reserved column, now used: t-shirt selection. */
    .log-pick {
      grid-area:  pick;
      align-self: start;
      margin:     0.3rem 0 0;
      accent-color: var(--gold);
      cursor:     pointer;
      flex:       0 0 auto;
    }

    .share-modal-backdrop {
      display: none;
      position: fixed; inset: 0;
      background: rgba(0,0,0,0.75);
      z-index: 1000;
      align-items: center;
      justify-content: center;
    }
    .share-modal-backdrop.open { display: flex; }
    .share-modal {
      background: var(--bg3);
      border: 1px solid var(--border2);
      border-radius: 6px;
      padding: 1.25rem 1.5rem 1.5rem;
      max-width: 460px;
      width: 92%;
      box-shadow: 0 8px 32px rgba(0,0,0,0.6);
    }
    .share-modal-header {
      display: flex;
      align-items: center;
      justify-content: space-between;
      margin-bottom: 1rem;
    }
    .share-modal-title {
      font-family: var(--mono);
      font-size: 0.72rem;
      letter-spacing: 0.12em;
      text-transform: uppercase;
      color: var(--text);
    }
    .share-modal-close {
      background: none; border: none; cursor: pointer;
      color: var(--text-dim); font-size: 1.2rem; line-height: 1;
      padding: 0 0.2rem;
    }
    .share-modal-close:hover { color: var(--text); }
    .share-modal textarea {
      width: 100%; box-sizing: border-box;
      background: var(--bg); border: 1px solid var(--border);
      border-radius: var(--radius);
      color: var(--text); font-family: var(--mono); font-size: 0.72rem;
      line-height: 1.6;
      padding: 0.75rem; resize: none; height: 180px;
    }
    .share-modal-btns { display: flex; gap: 0.5rem; margin-top: 0.75rem; justify-content: flex-end; }

    /* Inline data panel below eclipse list */
    #data-panel .panel {
      background:    transparent;
      border:        none;
      border-radius: 0;
      margin-bottom: 0;
      padding:       0;
    }


    .partial-slider-wrap {
      display:     flex;
      align-items: center;
      gap:         0.35rem;
      flex-shrink: 0;
    }
    .partial-slider-wrap input[type=range] {
      width:   5rem;
      padding: 0;
      border:  none;
      background: transparent;
      accent-color: var(--text-dim);
    }
    .partial-min-label {
      font-family: var(--mono);
      font-size:   0.65rem;
      color:       var(--text-dim);
      white-space: nowrap;
      min-width:   2.5rem;
    }

    .pill.pill-today {
      font-style:   italic;
      color:        var(--text-dim);
      border-color: var(--edge);
    }
    .pill.pill-today.active {
      background:   rgba(200,169,110,0.12);
      border-color: var(--edge);
      color:        var(--gold);
    }


    /* ── Map tab ─────────────────────────────────────────────────────── */

    #map {
      position: absolute;
      inset:    0;
      width:    100%;
      height:   100%;
    }

    /* Terrain-shadow toggle — top-left overlay button */
    /* ── On-map controls ──────────────────────────────────────────────────
       Overlays (toggles, independent) sit top LEFT; the basemap choice
       (exclusive, one of three) sits top RIGHT. Opposite corners so the two
       kinds of control are never mistaken for one another, and the bottom edge
       stays clear for the scrubber and the tab bar. */
    .map-overlay-btns {
      position: absolute;
      top:      0.6rem;
      left:     0.6rem;
      z-index:  200;
      display:  flex;
      gap:      0.4rem;
    }
    /* Icon-only. The glyphs carry the meaning: a hard-edged half-lit circle
       (terrain shadow) beside a soft cloud outline — different silhouettes, so
       they separate at a glance where two words would just be two words. */
    .map-overlay-btn {
      width:           2.1rem;
      height:          2.1rem;
      display:         flex;
      align-items:     center;
      justify-content: center;
      background:      rgba(10,12,15,0.88);
      border:          1px solid var(--border2);
      border-radius:   var(--radius);
      color:           var(--text-dim);
      font-size:       1.15rem;
      line-height:     1;
      cursor:          pointer;
      backdrop-filter: blur(4px);
      pointer-events:  auto;
    }
    .map-overlay-btn:hover                { border-color: var(--edge); color: var(--text); }
    .map-overlay-btn[aria-pressed="true"] { border-color: var(--gold); color: var(--gold); }
    .map-overlay-btn:disabled,
    .map-overlay-btn[aria-disabled="true"] { opacity: 0.4; cursor: not-allowed; }

    /* Right-hand map controls: basemap picker + locate pin, one row.
       The wrapper owns the anchoring so neither child positions itself — they
       used to share top/right 0.6rem and z-index 200, which meant the picker
       (later in the DOM) painted over the pin on every viewport. */
    .map-right-controls {
      position:    absolute;
      top:         0.6rem;
      right:       0.6rem;
      z-index:     200;
      display:     flex;
      align-items: flex-start;
      gap:         0.4rem;
    }

    /* Basemap picker: three small drawn map fragments, no words. Live provider
       tiles were tried first — at 44px a zoomed-out topo tile is
       indistinguishable from a street one. The swatches are inline SVG (see
       PICKER_SWATCH in js/map.js), so nothing is fetched: no pop-in, and the
       control looks the same offline as on. */
    .basemap-picker {
      display:  inline-flex;
      gap:      0.3rem;
      padding:  0.3rem;
      background:      rgba(10,12,15,0.88);
      border:          1px solid var(--border2);
      border-radius:   var(--radius);
      backdrop-filter: blur(4px);
      pointer-events:  auto;
    }

    /* The pin matches the picker's chrome and its tiles' footprint, so the two
       read as one row of controls rather than two unrelated widgets. */
    #btn-locate {
      display:         flex;
      align-items:     center;
      justify-content: center;
      width:           3.2rem;
      height:          3.2rem;
      padding:         0;
      font-size:       1.3rem;
      line-height:     1;
      cursor:          pointer;
      background:      rgba(10,12,15,0.88);
      border:          1px solid var(--border2);
      border-radius:   var(--radius);
      backdrop-filter: blur(4px);
      pointer-events:  auto;
    }
    #btn-locate:hover { border-color: var(--edge); }
    .basemap-picker.is-offline { opacity: 0.4; }
    .basemap-opt {
      width:         2.6rem;
      height:        2.6rem;
      padding:       0;
      background:    none;
      /* Transparent on every tile so the active one doesn't resize when it
         lights up — the reserved width must match .active's. Change both or
         neither. 1px reads as a hairline at any density; the old 1.5px plus an
         outer glow made a band rather than a ring. */
      border:        1px solid transparent;
      border-radius: 5px;
      cursor:        pointer;
      overflow:      hidden;
      opacity:       0.72;
      transition:    opacity 0.12s, border-color 0.12s;
    }
    .basemap-swatch, .basemap-swatch svg {
      display: block;
      width:   100%;
      height:  100%;
    }
    .basemap-swatch { border-radius: 3.5px; overflow: hidden; }
    .basemap-opt:hover    { opacity: 1; border-color: var(--edge); }
    /* The active one is simply the bright, ringed one — no tick, no label.
       The 1px inset shadow is a dark gap between swatch and ring, so the gold
       reads as a ring around the tile rather than a bright edge on it — the
       swatch is flush to the border under overflow:hidden, and without the gap
       a pale basemap and the gold blend into one another. */
    .basemap-opt.active   { opacity: 1; border-color: var(--gold);
                            box-shadow: inset 0 0 0 1px rgba(10,12,15,0.75); }
    .basemap-opt:disabled { cursor: not-allowed; }

    /* Phones: shrink rather than hide — the picker is the only way to change
       basemap now that the Settings pulldown is gone. */
    @media (max-width: 899px) {
      /* Collapsed picker: only the ACTIVE tile shows, so the control still
         reports which basemap is live while taking one tile of width instead of
         three. Tapping it adds .expanded (see renderBasemapPicker in map.js). */
      /* Slide, don't blink: the hidden tiles keep their box and collapse their
         width, so opening the picker reads as one control widening rather than
         two more appearing from nowhere. `display:none` cannot be animated,
         which is why width/opacity carry it instead. */
      .basemap-picker .basemap-opt {
        transition: width 0.18s ease, opacity 0.14s ease,
                    margin 0.18s ease, border-color 0.12s;
      }
      .basemap-picker:not(.expanded) .basemap-opt:not(.active) {
        width:         0;
        opacity:       0;
        margin-left:   -0.3rem;   /* swallow the flex gap while collapsed */
        border-width:  0;
        pointer-events: none;
      }

      /* Everything a notch smaller here — three clusters across a 360px phone
         needs the room more than it needs the tap target. */
      .basemap-opt    { width: 1.95rem; height: 1.95rem; }
      .map-overlay-btn { width: 1.9rem; height: 1.9rem; font-size: 1.05rem; }
      /* Matches the picker chip's height: tile + its 2 x 0.3rem padding + borders. */
      #btn-locate     { width: 2.7rem; height: 2.7rem; font-size: 1.1rem; }
    }

    /* Terrain-shadow time scrubber — flush full-width bar at the bottom. Left:
       selected date/time on two lines. Right: a ruler whose strip slides past a
       fixed centre needle. Shown only while shadows are on. */
    .shadow-timeline {
      position:        absolute;
      left:            0;
      right:           0;
      /* The PANEL runs to the very bottom of the screen so there is no stripe
         of map showing beneath it; the CONTROLS are lifted clear of the iOS
         home indicator by padding instead. At bottom:0 with no padding the
         scrubber sat in the system's swipe-up gesture zone and dragging it
         switched apps. viewport-fit=cover is already set on the viewport meta,
         so env() reports the real inset, and the 0px fallback leaves every
         other platform pixel-identical. */
      bottom:          0;
      padding-bottom:  env(safe-area-inset-bottom, 0px);
      box-sizing:      content-box;
      z-index:         20;
      display:         flex;
      align-items:     stretch;
      height:          34px;
      background:      rgba(10,12,15,0.92);
      border-top:      1px solid var(--border2);
      overflow:        hidden;
      backdrop-filter: blur(4px);
    }
    .shadow-timeline[hidden] { display: none; }
    /* Installed PWA on a device with a home indicator: a little extra clearance
       so the SCRUBBER TRACK itself, not just the panel edge, is out of the
       gesture zone. Browsers ignore this (display-mode: browser). */
    @media all and (display-mode: standalone) {
      /* A little extra so the scrubber TRACK, not just the panel edge, is out
         of the gesture zone. Browsers ignore this (display-mode: browser). */
      .shadow-timeline { padding-bottom: calc(env(safe-area-inset-bottom, 0px) + 8px); }
    }

    .shadow-readout {
      flex:          0 0 auto;
      display:       flex;
      flex-direction: column;
      justify-content: center;
      padding:       0 0.7rem;
      border-right:  1px solid var(--border2);
      font-family:   var(--mono);
      line-height:   1.15;
      user-select:   none;
      -webkit-user-select: none;
    }
    /* Fixed width, or the ruler slides sideways under the static needle whenever
       the text changes length ("local" is longer than "UTC"). Sized in ch — the
       stack is genuinely monospace, so 1ch is one character. Both lines max out
       at 11: "31 Dec 2026" and "14:32 local". Per-child min-width (rather than a
       width on the parent) keeps this correct if either font-size changes. */
    .shadow-date { font-size: 0.6rem;  color: var(--text-dim); white-space: nowrap; min-width: 11ch; }
    .shadow-clock{ font-size: 0.72rem; color: var(--gold2);     white-space: nowrap; min-width: 11ch; }

    .shadow-ruler-wrap {
      position:    relative;
      flex:        1 1 auto;
      height:      100%;
      overflow:    hidden;
      cursor:      ew-resize;
      touch-action: none;
      outline:     none;
      user-select: none;
      -webkit-user-select: none;
    }
    .shadow-ruler {
      position:    absolute;
      top:         0;
      left:        0;
      height:      100%;
      will-change: transform;
    }
    .shadow-tick {
      position:   absolute;
      bottom:     0;
      width:      1px;
      height:     5px;
      background: var(--border2);
    }
    .shadow-tick.major { height: 9px; background: var(--text-dim); }
    .shadow-tick-label {
      position:    absolute;
      bottom:      11px;
      transform:   translateX(-50%);
      font-family: var(--mono);
      font-size:   0.6rem;
      color:       var(--text-dim);
      white-space: nowrap;
    }
    .shadow-center {
      position:      absolute;
      left:          50%;
      top:           0;
      bottom:        0;
      width:         0;
      border-left:   2px solid var(--gold);
      pointer-events: none;
    }
    .shadow-center::after {
      content:      '';
      position:     absolute;
      top:          0;
      left:         -5px;
      border-left:  5px solid transparent;
      border-right: 5px solid transparent;
      border-top:   6px solid var(--gold);
    }

    /* Zoomed-out hint replaces the readout + ruler */
    .shadow-hint {
      display:     none;
      flex:        1;
      align-items: center;
      justify-content: center;
      font-family: var(--mono);
      font-size:   0.68rem;
      color:       var(--text-dim);
      user-select: none;
      -webkit-user-select: none;
    }
    .shadow-timeline.hint .shadow-readout,
    .shadow-timeline.hint .shadow-ruler-wrap { display: none; }
    .shadow-timeline.hint .shadow-hint       { display: flex; }

    /* MapLibre attribution — restyle to match dark theme */
    .maplibregl-ctrl-attrib {
      background: rgba(10,12,15,0.8) !important;
      color:      var(--text-dim) !important;
      font-size:  0.6rem !important;
    }
    .maplibregl-ctrl-attrib a { color: var(--text-dim) !important; }

    /* On phones the expanded attribution eats a whole strip of the map. MapLibre
       already ships a collapsed mode — the compact class plus its (i) toggle —
       but only applies it at its own breakpoint. Force it below 900px: the text
       hides behind the (i) and reappears when tapped, so credit is still one tap
       away and nothing is removed. Desktop keeps the inline text. */
    @media (max-width: 899px) {
      .maplibregl-ctrl-attrib.maplibregl-compact {
        min-height:    24px;
        padding:       0;
        margin:        0 8px 8px 0;
        border-radius: 12px;
      }
      .maplibregl-ctrl-attrib.maplibregl-compact:not(.maplibregl-compact-show)
        .maplibregl-ctrl-attrib-inner { display: none; }
      .maplibregl-ctrl-attrib.maplibregl-compact-show
        .maplibregl-ctrl-attrib-inner { display: block; }
    }

    /* Click-result popup */
    .map-popup {
      position:      absolute;
      bottom:        0.75rem;
      left:          0.75rem;
      z-index:       10;
      background:    rgba(10,12,15,0.92);
      border:        1px solid var(--border2);
      border-radius: var(--radius);
      padding:       0.75rem 1rem;
      font-family:   var(--mono);
      font-size:     0.72rem;
      display:       none;
      backdrop-filter: blur(4px);
      width:         max-content;
      max-width:     min(340px, calc(100vw - 1.5rem));
    }
    .map-popup-close {
      float:       right;
      background:  none;
      border:      none;
      color:       var(--text-dim);
      cursor:      pointer;
      font-size:   1rem;
      line-height: 1;
      padding:     0;
      margin-left: 0.5rem;
    }
    .map-popup-close:hover { color: var(--text); }
    .map-popup-title {
      color:         var(--text);
      margin-bottom: 0.4rem;
    }
    .map-popup-grid {
      display:               grid;
      grid-template-columns: 1fr 1fr;
      gap:                   0.2rem 1rem;
      color:                 var(--text);
    }
    .map-popup-label { color: var(--text-dim); font-size: 0.65rem; }

    /* Sun direction indicator on observer marker.
       The observer is a DRAWN PIN (see pinImage() in js/map.js) whose TIP is the
       coordinate; the arrow is a separate co-located marker that rotates about
       that same point. (.observer-dot / .ge-dot retired with the Cesium build.) */
    /* deck.gl's overlay is added with map.addControl(), so its canvas lives in a
       .maplibregl-ctrl-* corner container — and MapLibre gives those z-index:2
       while .maplibregl-marker gets none. Without this the eclipse paths draw
       OVER the pin, arrow and GE diamond. Markers belong on top of the lines. */
    .maplibregl-marker {
      z-index: 3;
    }
    /* Same rule as .observer-pin-wrap: no `position` declaration. The absolute
       positioning inherited from .maplibregl-marker is itself the containing
       block .sun-arrow needs, and it keeps the wrapper out of normal flow. */
    /* Separates the map-overlays block at the end of Instructions. A rule read
       as a hard structural break; an ornament reads as a breath. Sun, moon and
       star — dimmed and letter-spaced so it decorates rather than announces. */
    .instructions-ornament {
      margin:         1.4rem 0 1.1rem;
      text-align:     center;
      color:          var(--border2);
      font-size:      0.95rem;
      letter-spacing: 0.35em;
      line-height:    1;
      user-select:    none;
    }

    .sun-arrow-wrap {
      width:  44px;
      height: 44px;
    }
    /* The wrapper is the MARKER element (MapLibre owns its transform); the
       image inside carries the zoom scale. Size comes from PIN_W/PIN_H in
       js/map.js, which also draw the artwork — one source, no drift.
       Do NOT set `position` here: .maplibregl-marker is position:absolute, and
       overriding it to relative drops the wrapper into normal flow, where a
       preceding in-flow marker shifts it off the coordinate. */
    .observer-pin-wrap {
      pointer-events: none;
    }
    .observer-pin {
      display:           block;
      max-width:         none;
      max-height:        none;
      pointer-events:    none;
      user-select:       none;
      -webkit-user-drag: none;
      filter: drop-shadow(0 1px 2px rgba(0,0,0,0.35));
    }
    /* Cesium proportions: total 44px, head 30% of length (13.2px),
       head half-width 0.42 x head length (5.5px); stem is the remaining 70%. */
    .sun-arrow {
      position:         absolute;
      top:              50%;
      left:             50%;
      width:            30.8px;
      height:           2.6px;
      background:       var(--pin-red);
      transform-origin: 0 50%;
      opacity:          0.9;
    }
    .sun-arrow::after {
      content:       '';
      position:      absolute;
      right:         -13.2px;
      top:           -4.2px;
      border-left:   13.2px solid var(--pin-red);
      border-top:    5.5px solid transparent;
      border-bottom: 5.5px solid transparent;
    }
    /* Greatest eclipse — orange diamond, deliberately distinct from the red observer. */
    .ge-diamond {
      width:      12px;
      height:     12px;
      background: #f08a1e;
      border:     1.5px solid rgba(255,255,255,0.92);
      transform:  rotate(45deg);
      box-shadow: 0 0 6px rgba(240,138,30,0.55);
    }


    /* ── Responsive ──────────────────────────────────────────────────── */

    @media (max-width: 600px) {
      /* Hide the branding banner on mobile browser — screen space is precious and
         Safari's own chrome already eats the top, so the tab bar becomes the top
         edge. --header-h:0 lets the panel max-height calc reclaim the space.
         (In the installed PWA the banner is already hidden via display-mode.) */
      :root { --header-h: 0rem; }
      .app-header   { display: none; }
      .tab-btn      { font-size: 1.1rem; letter-spacing: 0; }
      .tab-label    { display: none; }
      .tab-panel    { padding: 0.75rem 0.75rem 6rem; }

      /* Larger base font for readability on phone */
      body { font-size: 1.05rem; }
      .eclipse-item { font-size: 1rem; padding: 0.75rem; }
      .search-hint  { font-size: 0.78rem; }
      .pill         { font-size: 0.8rem; }
      .section-label { font-size: 0.85rem; }
      .top-section-header, .detail-section-h { font-size: 0.95rem; }
      .detail-table .l    { font-size: 0.72rem; }
      .detail-table .v    { font-size: 0.95rem; }
      .detail-table td    { padding: 0.4rem 0; }

      /* Hide map popup on mobile — it renders off-screen */
      .map-popup { display: none !important; }
    }


    /* ── Sidebar sub-tab buttons (DOM-present on all viewports;
          visually hidden on mobile, shown on desktop) ─────────────────── */

    .sidebar-tabs { display: none; }

    .sidebar-tab-btn {
      flex:           1;
      background:     var(--bg2);
      color:          var(--text-dim);
      border:         1px solid var(--border);
      border-bottom:  none;
      border-radius:  var(--radius) var(--radius) 0 0;
      margin-right:   -1px;
      padding:        0.45rem 0.35rem 0.5rem;
      font-family:    var(--mono);
      font-size:      0.72rem;
      letter-spacing: 0.04em;
      cursor:         pointer;
      white-space:    nowrap;
      position:       relative;
    }
    .sidebar-tab-btn:last-child { margin-right: 0; }
    .sidebar-tab-btn:hover      { color: var(--text); }
    .sidebar-tab-btn.active {
      color:         var(--gold);
      background:    #1a2030;
      margin-bottom: -1px;
      z-index:       2;
    }


    /* ── Desktop layout: two-column grid with right-side sidebar ─────── */

    @media (min-width: 900px) {

      /* No GPS on a desktop — browser geolocation there resolves by IP or
         Wi-Fi and lands you in the wrong town, which is worse than useless for
         an app whose whole point is where the shadow falls to the kilometre.
         Click the map or type coordinates instead. The startup geolocation
         request in js/init.js is unaffected; this hides only the manual
         re-trigger. */
      #btn-locate { display: none; }

      /* Two-column flex inside .tab-content: map flexes, sidebar fixed-width */
      .tab-content {
        display:        flex;
        flex-direction: row;
      }

      /* Bottom tab-bar (mobile only) is hidden on desktop */
      .tab-bar { display: none; }

      /* Map: always visible, dominant left column */
      #tab-map {
        position: relative;
        inset:    auto;
        flex:     1 1 auto;
        display:  block;
        padding:  0;
      }

      /* Sidebar: fixed-width right column with sub-tabs on top */
      .sidebar {
        display:        flex;
        flex-direction: column;
        flex:           0 0 360px;
        border-left:    1px solid var(--border);
        background:     #1a2030;
        overflow:       hidden;
      }
      .sidebar-tabs {
        display:        flex;
        flex-direction: row;
        padding:        0.5rem 0.5rem 0;
        background:     #040508;
        border-bottom:  1px solid var(--border);
      }

      /* Panels inside sidebar: one shown at a time based on data-sidebar-tab. */
      .sidebar .tab-panel {
        position:  relative;
        inset:     auto;
        flex:      1 1 auto;
        display:   none;
        padding:   0.75rem 1rem 1rem;
      }
      .sidebar .tab-panel > * { max-width: none; margin: 0; }

      body[data-sidebar-tab="search"]   #tab-search,
      body[data-sidebar-tab="eclipse"]  #tab-eclipse,
      body[data-sidebar-tab="log"]      #tab-log,
      body[data-sidebar-tab="settings"] #tab-settings {
        display: block;
      }

      /* Hide overlays whose information now lives in the sidebar:
         - #map-status     (eclipse name/type pill at bottom of map)
         - #map-popup      (map-click circumstances; Details panel shows them) */
      #map-status, #map-popup { display: none !important; }
    }


/* ── Sun-track diagram (Details panel) ─────────────────────────────────── */
#suntrack { margin: 2px 0 6px; }
.st-svg {
  width: 100%; height: auto; display: block;
  border: 1px solid var(--gold-dim);
  border-radius: 6px;
}
.st-track   { fill: none; stroke: rgba(255,255,255,0.55); stroke-width: 1.25; stroke-dasharray: 2 3; }
.st-horizon { stroke: rgba(255,255,255,0.35); stroke-width: 1; stroke-dasharray: 3 3; }
.st-hlbl    { fill: rgba(255,255,255,0.6); font-size: 9px; }
.st-contact { fill: #fff; stroke: rgba(0,0,0,0.5); stroke-width: 1; }
.st-clbl    { fill: #fff; font-size: 10px; paint-order: stroke; stroke: rgba(0,0,0,0.55); stroke-width: 2px; }
.st-sun     { fill: #ffe08a; filter: drop-shadow(0 0 5px rgba(255,210,120,0.9)); }
.st-moon    { fill: #0a0c10; stroke: rgba(255,255,255,0.25); stroke-width: 0.5; }
.st-slider  { width: 100%; margin: 5px 0 1px; accent-color: var(--gold); }
.st-readout { font-size: 11px; color: var(--gold2); text-align: center; font-variant-numeric: tabular-nums; white-space: nowrap; }

/* Floating map status overlay (eclipse label / "tap to set location") */
.map-status {
  /* Bottom-LEFT, not centred: centred, it sat right where the paths and the pin are,
     and fought the terrain for legibility. Cornered, it's always findable and never
     covers the thing you're looking at. */
  position: absolute; bottom: 0.85rem; left: 0.85rem;
  font-family: var(--mono); font-size: 0.78rem; color: var(--text);
  font-weight: 700; letter-spacing: 0.02em;
  pointer-events: none; background: var(--bg2);
  padding: 0.3rem 0.85rem; border-radius: 99px; opacity: 1;
  box-shadow: 0 1px 6px rgba(0,0,0,0.35);
  white-space: nowrap;          /* it was wrapping mid-date */
  max-width: calc(100% - 1.5rem);
  display: none;
}

/* Time-mode toggle arrows affordance */
.tm-switch { color: var(--text); cursor: pointer; text-decoration: underline dotted; text-underline-offset: 2px; }
.tm-switch:hover { color: var(--text-pale); }

.tol-note { margin-top: 0.5rem; }

.ct-row { cursor: pointer; }
.ct-row:hover td { background: rgba(200,169,110,0.12); }


/* ── Settings / About body text ────────────────────────────────────────────
   These panels were inheriting a larger base size than the rest of the app, so on
   mobile the prose read noticeably bigger than everything else. Pin them to the
   standard body size. */
#tab-settings p,
#tab-settings li,
#tab-settings .settings-group > div,
#tab-settings .top-section-header + p {
  font-size: 0.75rem;      /* matches the app's standard body size (was ~1rem → read as huge) */
  line-height: 1.55;
}


/* ── Asset loading crawlbar ────────────────────────────────────────────────
   A 1px white bar riding the banner's bottom hairline. Indeterminate: it crawls
   while any tracked fetch is in flight, so "the page is up but still loading" is
   visible rather than guessed at. */
.load-bar {
  position: relative;
  height: 1px;
  width: 100%;
  overflow: hidden;
  background: transparent;
  opacity: 0;
  transition: opacity 0.25s ease;
  pointer-events: none;
  z-index: 50;
}
.load-bar.active { opacity: 1; }
.load-bar-run {
  position: absolute;
  top: 0; left: 0;
  height: 100%;
  width: 22%;
  /* Solid segment with only slightly softened ends — a steady crawl. The old
     transparent→white→transparent gradient pulsed like a lightning zap. */
  background: rgba(255,255,255,0.85);
  border-radius: 1px;
  animation: load-crawl 2.4s linear infinite;   /* slower, calmer */
}
@keyframes load-crawl {
  0%   { transform: translateX(-100%); }
  100% { transform: translateX(555%); }
}

/* ── Hamburger throb: fresh local circumstances are waiting in Details ────────
   Brightens as well as swells. `fill` carries the colour change (a brightness
   multiply alone only shifts value, which reads as a wobble).

   The filter list must hold the SAME functions in the SAME order at every stop:
   CSS interpolates filter lists smoothly only when they match, and otherwise
   falls back to discrete swapping, which snaps at the midpoint. So the glow is
   always present and only its blur radius grows from 0, and brightness is always
   present and only its factor rises. The base drop-shadow from .tab-icon-svg is
   repeated here too, since a keyframe `filter` replaces the base one outright.
   Raise brightness() for a hotter flash. */
@keyframes tab-throb {
  0%, 100% {
    transform: scale(1);
    fill:      var(--gold);
    filter:    drop-shadow(0 1px 1px rgba(0,0,0,0.35))
               drop-shadow(0 0 0 var(--gold2))
               brightness(1);
  }
  50% {
    transform: scale(1.18);
    fill:      var(--gold2);
    filter:    drop-shadow(0 1px 1px rgba(0,0,0,0.35))
               drop-shadow(0 0 6px var(--gold2))
               brightness(1.6);
  }
}
@media (max-width: 899px) {
  .tab-btn.has-fresh .tab-icon-svg {
    animation: tab-throb 1.1s ease-in-out 1;    /* a single pulse is enough */
    transform-origin: center;
  }
}

/* ── Phone in LANDSCAPE: reclaim vertical space ────────────────────────────
   Browser chrome + our banner + tab bar left almost nothing for the map. In a short
   landscape viewport, drop the banner entirely (the tab bar already identifies the
   app) and tighten the tabs. */
@media (max-width: 950px) and (orientation: landscape) and (max-height: 500px) {
  :root { --header-h: 0rem; }
  .app-header { display: none; }

  .tab-btn {
    padding: 0.3rem 0.55rem;
    font-size: 0.66rem;
  }
  .tab-label { display: none; }        /* icons only — the labels are the fat here */
  .tab-icon-svg { width: 1em; height: 1em; }

  .map-status { bottom: 1.2rem; font-size: 0.7rem; padding: 0.2rem 0.6rem; }
}

/* ── Install prompt ────────────────────────────────────────────────────────
   Mobile BROWSERS only. Hidden on desktop (irrelevant) and in the installed
   standalone app (<html class="is-standalone">, where it would be absurd). */
.install-note { display: none; }
@media (max-width: 899px) {
  html:not(.is-standalone) .install-note {
    display: block;
    margin-top: 0.6rem;
    padding: 0.5rem 0.7rem;
    border-left: 2px solid var(--border2);
    background: rgba(200,169,110,0.08);
  }
}
