
:root{--ink:#07133c;--muted:#56627d;--paper:#f7f8ff;--panel:#fff;--line:#dce1f4;--blue:#162adf;--pink:#ee00c6;--cyan:#48d8f5;--shadow:0 16px 42px rgba(8,19,61,.13);font-family:Calibri,"Segoe UI",system-ui,sans-serif}*{box-sizing:border-box}body{margin:0;background:radial-gradient(circle at 8% 0,rgba(72,216,245,.22),transparent 29rem),radial-gradient(circle at 92% 8%,rgba(238,0,198,.14),transparent 27rem),var(--paper);color:var(--ink)}a{color:inherit}.site-header{position:sticky;top:0;z-index:5;background:rgba(247,248,255,.91);backdrop-filter:blur(16px);border-bottom:1px solid var(--line)}.nav{max-width:1220px;margin:auto;padding:.7rem 1.1rem;display:flex;align-items:center;gap:1rem}.brand{display:flex;align-items:center;gap:.55rem;font-size:1.25rem;font-weight:800;text-decoration:none}.brand img{width:34px;height:42px;object-fit:contain}.nav-links{display:flex;margin-left:auto;gap:.18rem;flex-wrap:wrap}.nav-links a{padding:.55rem .8rem;border-radius:999px;text-decoration:none;color:var(--muted);font-weight:700}.nav-links a.active,.nav-links a:hover{background:#e9edff;color:var(--blue)}.menu-toggle{display:none;margin-left:auto}.hero{max-width:1220px;margin:auto;padding:2.2rem 1.1rem;display:grid;grid-template-columns:1.15fr .85fr;gap:1.2rem}.hero-copy,.hero-image,.panel,.media-card,.feature-card,.about-card{border:1px solid var(--line);box-shadow:var(--shadow);border-radius:24px;background:rgba(255,255,255,.9)}.hero-copy{padding:clamp(1.5rem,4vw,4rem);display:flex;flex-direction:column;justify-content:center}.eyebrow{margin:0;color:var(--blue);font-size:.78rem;text-transform:uppercase;letter-spacing:.12em;font-weight:800}h1{font-size:clamp(2.65rem,6vw,5.8rem);line-height:.91;letter-spacing:-.065em;margin:.55rem 0 1.1rem}h2{font-size:clamp(1.7rem,3vw,2.6rem);letter-spacing:-.04em;margin:0}.hero p,.page-intro p{color:var(--muted);font-size:1.12rem;line-height:1.5;max-width:47rem}.hero-image{min-height:440px;overflow:hidden;position:relative;background:#07133c}.hero-image img{width:100%;height:100%;object-fit:cover;position:absolute;inset:0}.hero-image:after{content:"Lake 75\A wildlife · water · weather";white-space:pre-wrap;position:absolute;bottom:1.2rem;left:1.2rem;color:#fff;padding:.9rem 1rem;border-radius:16px;background:rgba(7,19,60,.68);font-weight:700}.actions{display:flex;flex-wrap:wrap;gap:.7rem;margin-top:1.3rem}.button{border:1px solid var(--line);display:inline-flex;justify-content:center;align-items:center;min-height:44px;padding:.7rem 1rem;border-radius:999px;background:#fff;text-decoration:none;font-weight:800}.button.primary{background:var(--blue);border-color:var(--blue);color:#fff}.button.pink{background:var(--pink);border-color:var(--pink);color:#fff}main{max-width:1220px;margin:auto;padding:1rem 1.1rem 4rem}.section-head{display:flex;justify-content:space-between;align-items:end;gap:1rem;margin:2.6rem 0 1rem}.section-head p{color:var(--muted);margin:.3rem 0 0}.module-grid{display:grid;grid-template-columns:repeat(4,1fr);gap:1rem}.module-card{min-height:var(--module-card-height,265px);border-radius:24px;padding:1.25rem;color:#fff;box-shadow:var(--shadow);text-decoration:none;display:flex;flex-direction:column;justify-content:space-between;overflow:hidden;position:relative}.module-card:after{content:"";width:180px;height:180px;border:1px solid rgba(255,255,255,.24);border-radius:50%;position:absolute;right:-50px;top:-55px}.module-card .icon{font-size:2.5rem;z-index:1}.module-card h3{font-size:1.65rem;margin:0;z-index:1}.module-card p{margin:.35rem 0 0;color:rgba(255,255,255,.85);z-index:1}.trail{background:linear-gradient(140deg,#07135f,#1d38e9 60%,#17b9ba)}.time{background:linear-gradient(140deg,#512883,#cf59b8 58%,#fa944e)}.pano{background:linear-gradient(140deg,#06341f,#26895f 58%,#76d9b6)}.photo{background:linear-gradient(140deg,#593217,#b56a35 60%,#f1b66d)}.latest-grid,.media-grid{display:grid;grid-template-columns:repeat(3,1fr);gap:1rem}.media-card{overflow:hidden}.media-card img{width:100%;aspect-ratio:16/9;object-fit:cover;display:block;background:#142561}.media-card.photo-card img{aspect-ratio:4/3;object-fit:contain}.card-body{padding:1rem}.card-body h3{margin:0 0 .4rem;font-size:1.12rem}.card-body p{color:var(--muted);font-size:.94rem;line-height:1.4}.meta{display:flex;gap:.5rem;flex-wrap:wrap;color:var(--muted);font-size:.85rem}.tags{display:none;flex-wrap:wrap;gap:.35rem;margin:.7rem 0}.tag{font-size:.78rem;color:var(--blue);background:#e9edff;border-radius:999px;padding:.23rem .48rem;cursor:pointer}.page-intro{padding:3rem 0 1.2rem}.page-intro h1{font-size:clamp(2.8rem,6vw,5rem)}.panel{padding:1rem}.filters{display:grid;grid-template-columns:1.1fr repeat(4,.8fr);gap:.7rem;align-items:end}.filters label{font-weight:700;color:var(--muted);font-size:.85rem}input,select,textarea{width:100%;min-height:42px;margin-top:.3rem;padding:.6rem .7rem;border:1px solid var(--line);border-radius:12px;background:#fff;font:inherit;color:var(--ink)}textarea{min-height:8rem;resize:vertical}.filters p{margin:0;color:var(--muted)}.open-search{max-width:32rem}.open-search label{font-weight:700;color:var(--muted);font-size:.85rem;display:block}.open-search p{margin:.5rem 0 0;color:var(--muted)}.lw-hp{position:absolute;left:-9999px;top:-9999px;width:1px;height:1px;overflow:hidden}.contact-panel{margin-top:2rem}.contact-form{display:flex;flex-direction:column;gap:.3rem;max-width:32rem}.contact-form label{font-weight:700;color:var(--muted);font-size:.85rem;margin-top:.5rem}.contact-form button{margin-top:1rem;align-self:flex-start}.lw-contact-status{font-weight:600;color:var(--blue)}.about-layout{display:grid;grid-template-columns:1fr 1fr;gap:1rem}.about-card{padding:1.4rem;line-height:1.55}.about-card h2{font-size:1.6rem}.site-footer{text-align:center;border-top:1px solid var(--line);padding:2rem 1rem;color:var(--muted)}@media(max-width:850px){.nav{flex-wrap:wrap}.menu-toggle{display:block;border:1px solid var(--line);background:#fff;border-radius:10px;padding:.55rem .75rem}.nav-links{display:none;margin:0}.hero{grid-template-columns:1fr;padding-top:1.2rem}.hero-image{min-height:300px}.module-grid,.latest-grid,.media-grid,.about-layout,.filters{grid-template-columns:1fr}.module-card{min-height:200px}h1{font-size:clamp(2.6rem,13vw,4rem)}}

    .page-intro p{max-width:none}
    .content-picture-set{overflow:hidden;border-radius:14px;margin:0 0 1rem;background:#e9edff}.content-picture-set .content-image{display:block;width:100%;height:270px;object-fit:cover;transform:scale(var(--image-zoom,1));transform-origin:center;transition:transform .2s ease,filter .15s ease}.content-picture-set:has(.image-left),.content-picture-set:has(.image-right){width:43%}.content-picture-set:has(.image-left){float:left;margin:.1rem 1rem .7rem 0}.content-picture-set:has(.image-right){float:right;margin:.1rem 0 .7rem 1rem}.content-picture-set .content-image+.content-image{margin-top:1rem}
    .content-image{cursor:zoom-in;transition:filter .15s ease}.content-image:hover{filter:brightness(.9)}.about-card:has(.image-below){display:flex;flex-wrap:wrap}.about-card:has(.image-below)>h2,.about-card:has(.image-below)>p,.about-card:has(.image-below)>a{order:1;width:100%}.about-card .image-below{order:2}.about-card .image-left{float:left;width:43%;margin:.1rem 1rem .7rem 0}.about-card .image-right{float:right;width:43%;margin:.1rem 0 .7rem 1rem}.image-lightbox{width:min(1200px,96vw);max-height:96vh;border:0;border-radius:18px;padding:.8rem;background:#07133c}.image-lightbox::backdrop{background:rgba(7,19,60,.78)}.image-lightbox img{display:block;max-width:100%;max-height:88vh;margin:auto;object-fit:contain}.image-lightbox button{position:absolute;right:1rem;top:1rem;border:0;border-radius:50%;width:40px;height:40px;font-size:1.5rem}.media-card iframe{pointer-events:none}
    html,body{width:100%;min-width:320px;overflow-y:scroll;scrollbar-gutter:stable;font-family:Calibri,"Segoe UI",system-ui,sans-serif;font-size:16px}.site-header{height:64px!important;min-height:64px!important;max-height:64px!important;width:100%;box-sizing:border-box;isolation:isolate}.site-header .nav{width:100%;height:64px!important;min-height:64px!important;max-height:64px!important;box-sizing:border-box;padding:11px 18px}.site-header .brand,.site-header .nav-links a{font-family:Calibri,"Segoe UI",system-ui,sans-serif!important;line-height:1!important}.site-header .brand{flex:0 0 auto;font-size:20px!important}.site-header .nav-links{align-items:center;white-space:nowrap}.site-header .nav-links a{display:inline-flex;align-items:center;height:38px!important;min-height:38px!important;font-size:16px!important;line-height:16px!important}.brand-mark{display:block;width:19px;height:42px;flex:0 0 19px;background:url('../images/lakewatch-mark.png') left center/auto 42px no-repeat}
    .module-card.has-module-image{background-size:cover;background-position:center}.latest-grid a.media-card{display:block;text-decoration:none;transition:transform .18s ease,box-shadow .18s ease}
    .latest-grid a.media-card:hover{transform:translateY(-4px)}
    .media-card .play-hint{display:inline-flex;margin-top:.6rem;color:var(--blue);font-weight:800}
    .media-card[role="button"]{cursor:pointer;position:relative}.card-open{margin:.2rem 1rem 1rem;border:0;border-radius:999px;padding:.5rem .8rem;color:#fff;background:var(--blue);font-weight:800}
    .media-viewer h2{font-size:1.4rem;margin:.25rem 2.5rem .75rem}.media-viewer p{margin:.7rem 0;color:#dce7ff}.viewer-close{position:absolute;top:.65rem;right:.75rem;width:38px;height:38px;border:0;border-radius:50%;font-size:1.7rem;line-height:1;background:#fff;color:var(--ink);cursor:pointer}
    /* Round 18: .media-viewer's own box (width/max-height/background/
       shadow), its ::backdrop, and its base video/img sizing moved into
       the single consolidated viewer block (search "Round 18: media
       viewer" below) alongside the other three paths, instead of sitting
       here on their own. .viewer-next{...} removed — dead: every viewer
       button, including the "Next item" one, is styled by the winning
       .viewer-actions button rule in that same block, so this unused,
       non-!important duplicate never applied to anything. */
    .hero h1,.page-intro h1{font-size:clamp(1.7rem,3vw,2.6rem);line-height:1.05;letter-spacing:-.04em}.page-intro{padding:2.2rem 0 1.2rem}.hero .hero-subheading{font-size:1.12rem;font-weight:800;color:var(--ink);margin:.25rem 0 .35rem}.hero-copy{padding:clamp(1.4rem,3vw,2.2rem)}.hero-image video{width:100%;height:100%;object-fit:cover;position:absolute;inset:0}.latest-grid video,.latest-grid iframe,.media-card iframe{width:100%;aspect-ratio:16/9;border:0;display:block;background:#07133c}.media-card iframe{pointer-events:auto}.pano-preview{min-height:180px}.about-card img{width:100%;max-height:270px;object-fit:cover;border-radius:14px;margin:0 0 1rem}.home-extra-grid{display:grid;grid-template-columns:repeat(3,1fr);gap:1rem;margin:1rem 0 2rem}.slideshow-controls{display:flex;align-items:center;gap:.75rem;flex-wrap:wrap;margin:0 0 1rem}.slideshow-controls label{font-weight:700;color:var(--muted)}.slideshow-controls input{width:5rem;display:inline-block;margin-left:.35rem}.slideshow-controls span{color:var(--muted);font-size:.9rem}.photo-pick{display:block;margin:.7rem 1rem 0;font-size:.84rem;font-weight:700;color:var(--muted)}.photo-pick input{width:auto;min-height:0;margin:0 .2rem 0 0}@media(max-width:850px){.site-header{height:64px!important;min-height:64px!important;max-height:64px!important}.site-header .nav{height:64px!important;min-height:64px!important;max-height:64px!important;flex-wrap:nowrap}.home-extra-grid{grid-template-columns:1fr}}
    /* Consistent editable section cards: width, picture position and framing. */
    .about-layout,.home-extra-grid{display:grid;grid-template-columns:repeat(6,minmax(0,1fr))}.about-card{display:flex;flex-direction:column;gap:1rem;min-width:0}.about-card .section-copy{order:1;min-width:0}.section-width-third{grid-column:span 2}.section-width-half{grid-column:span 3}.section-width-two-thirds{grid-column:span 4}.section-width-full{grid-column:1 / -1}.content-picture-set{display:block;gap:0;width:100%;overflow:hidden;order:0;flex:0 0 auto;padding:0!important;background:transparent!important}.content-picture-set.image-below{order:2}.content-picture-set .content-image{width:100%;height:270px;display:block;object-fit:cover;object-position:var(--image-focus,center);background:transparent;margin:0!important;border-radius:14px}.content-picture-set .content-image.frame-fill{transform:scale(var(--image-zoom,1));transform-origin:var(--image-focus,center)}.content-picture-set:has(.frame-fill){overflow:hidden}.content-picture-set .content-image.frame-fit{height:auto;max-height:none;object-fit:contain;transform:none;background:transparent}.about-card:has(.image-left),.about-card:has(.image-right){display:grid;align-items:start;gap:1rem;grid-template-columns:minmax(0,.85fr) minmax(0,1.15fr)}.about-card:has(.image-left) .content-picture-set{grid-column:1;grid-row:1;order:0}.about-card:has(.image-left) .section-copy{grid-column:2;grid-row:1}.about-card:has(.image-right) .content-picture-set{grid-column:2;grid-row:1;order:0}.about-card:has(.image-right) .section-copy{grid-column:1;grid-row:1}.about-card .image-left,.about-card .image-right{float:none!important;width:100%!important;margin:0!important}
    /* Round 18: this line held a second, differently-valued, non-
       !important .viewer-actions base and the dead .media-viewer:fullscreen
       rules. The .viewer-actions base was redundant with the winning
       !important version in the consolidated viewer block below; the
       .media-viewer:fullscreen rules never matched anything — see that
       block's comment for how this was confirmed, not assumed. */
    @media(max-width:850px){.home-extra-grid,.about-layout{grid-template-columns:1fr}.section-width-third,.section-width-half,.section-width-two-thirds,.section-width-full{grid-column:1}.about-card:has(.image-left),.about-card:has(.image-right){display:flex}.about-card:has(.image-left) .content-picture-set,.about-card:has(.image-right) .content-picture-set,.about-card:has(.image-left) .section-copy,.about-card:has(.image-right) .section-copy{grid-column:auto;grid-row:auto}.viewer-actions button{min-width:0;flex:1 1 9rem}}

    .content-picture-set,.content-picture-set.image-above,.content-picture-set.image-below{display:block!important;width:100%!important;max-width:100%!important;background:transparent!important;padding:0!important;overflow:hidden!important;border-radius:14px!important;line-height:0!important}
    .content-picture-set .content-image{display:block!important;width:100%!important;height:270px!important;margin:0!important;border:0!important;border-radius:14px!important;background:transparent!important;object-fit:cover!important;object-position:var(--image-focus,center)!important}
    .content-picture-set .content-image.frame-fit{height:auto!important;max-height:none!important;object-fit:contain!important;transform:none!important}
    .content-picture-set .content-image.frame-fill{transform:scale(var(--image-zoom,1))!important;transform-origin:var(--image-focus,center)!important}
    /* ---- Round 18: media viewer sizing, four paths, one place ----------
       Previously spread across a dozen separate locations in this file,
       accumulated round by round since round 4, several pieces
       !important, with two real, independent bugs living in it — found
       by real-device testing, invisible to cascade reasoning and jsdom:
       an open nav panel painting transparently over page content (fixed
       separately, search .nav-links.open), and this component's mobile-
       overlay path forcing a full-height floor regardless of content.
       Restated here as one rule set, one principle in every path: size
       to content, capped so the action bar is always reachable without
       scrolling, no min-height forcing a floor — except the one path
       where a floor is genuinely correct.

       experience.js's openViewer() picks exactly one of these four:
         1. Plain <dialog>.showModal() — desktop, and any device where
            useMobileOverlay() is false and <dialog> is supported.
         2. shell.requestFullscreen(), reached from any of the other
            three — the sole deliberate exception to "no forced height":
            true OS fullscreen really does take over the whole screen.
            Targets .viewer-shell:fullscreen, not .media-viewer:fullscreen.
         3. .lw-dialog-fallback — browsers with no <dialog> support at
            all (old browsers only).
         4. .lw-mobile-viewer-overlay — narrow viewport, or a UA string
            containing iPhone/iPad/iPod (useMobileOverlay() in
            experience.js — confirmed by reading it directly, not
            assumed: matchMedia('(max-width:850px)') OR that regex, no
            touch-capability check). iOS behaves more reliably with an
            ordinary fixed element than <dialog>. This was the reported
            bug's path: its .viewer-shell
            carried min-height:calc(100dvh - 24px), forcing the shell to
            (near enough) full device height regardless of how little
            the actual photo/video/caption needed — media at the top, a
            large empty gap, then the action bar stranded at the bottom.
            Swapped to max-height, same value: a cap, not a floor,
            matching every other path's own principle. The same swap is
            applied to path 3 below for the same reason, though no
            device reaching that path was reported broken.

       No .media-viewer:fullscreen rules appear below: experience.js
       fullscreens .viewer-shell itself, not the <dialog> element, so a
       .media-viewer:fullscreen selector never matches anything on this
       site — confirmed by reading openViewer() and the fullscreen
       button handler, not assumed. The three copies of it that existed
       before round 18 (this block's old location, and mirrored against
       .pano-frame and .photo-stage further down) were dead code left
       over from before the fullscreen target changed to .viewer-shell —
       removed rather than kept as a fifth path that looks live but
       never runs. */

    /* -- Path 1: plain dialog -- */
    .media-viewer{width:min(1000px,94vw);max-height:94vh;border:0;border-radius:22px;padding:1rem;background:#07133c;color:#fff;box-shadow:0 24px 80px rgba(0,0,0,.42)}
    .media-viewer::backdrop{background:rgba(7,19,60,.72)}
    .media-viewer video,.media-viewer img{width:100%;max-height:74vh;object-fit:contain;background:#000}
    .viewer-shell{width:100%;max-height:94vh;min-height:0;overflow-y:auto}
    .viewer-actions{display:flex!important;flex-wrap:wrap!important;justify-content:center!important;gap:12px!important}
    /* Round 18a: appearance folded into the winning !important rule.
       Round 18 removed two non-!important .viewer-actions base blocks as
       redundant. They were not: the surviving rule sets geometry only, so
       every action button rendered white and square-cornered on every
       device. Colour, radius, border and weight now live in the one rule
       that already wins, so there is no redundant duplicate left to be
       removed again by a future cleanup. */
    .viewer-actions button{flex:0 0 140px!important;width:140px!important;min-width:140px!important;height:48px!important;min-height:48px!important;padding:0 10px!important;white-space:nowrap!important;border:0!important;border-radius:999px!important;background:var(--pink)!important;color:#fff!important;font-weight:800!important;cursor:pointer!important}
    .viewer-actions .viewer-fullscreen{background:var(--blue)!important}
    .viewer-nav{display:none}
    .viewer-nav button{border:0;border-radius:50%;width:54px;height:54px;background:rgba(255,255,255,.92);color:#11215e;font-size:2.2rem;line-height:1;cursor:pointer;box-shadow:0 4px 18px rgba(0,0,0,.35)}

    /* -- Path 2: :fullscreen — the one deliberate exception to "no forced height" -- */
    .viewer-shell:fullscreen{width:100vw!important;height:100vh!important;max-height:none!important;padding:18px!important;background:#07133c!important;color:#fff!important;display:flex!important;flex-direction:column!important;justify-content:center!important}
    .viewer-shell:fullscreen [data-viewer-content],.viewer-shell:fullscreen [data-content-image-content]{min-height:0!important;display:flex!important;flex:1!important;flex-direction:column!important;justify-content:center!important;align-items:center!important}
    .viewer-shell:fullscreen video,.viewer-shell:fullscreen img{display:block!important;width:100%!important;height:auto!important;max-width:100%!important;max-height:calc(100vh - 36px)!important;object-fit:contain!important}
    .viewer-shell:fullscreen .viewer-actions{display:none!important}
    .viewer-shell:fullscreen .viewer-nav{position:fixed!important;inset:0!important;display:flex!important;align-items:center!important;justify-content:space-between!important;padding:0 22px!important;pointer-events:none!important}
    .viewer-shell:fullscreen .viewer-nav button{pointer-events:auto!important}
    .viewer-shell:fullscreen .viewer-close{z-index:2}

    /* Task: photo metadata display. Same positioning pattern as
       .viewer-close above (position:absolute against .media-viewer's own
       containing block, no new position:relative rule added anywhere) —
       top-left, mirroring .viewer-close's top-right, so the two never
       collide. Hidden entirely in fullscreen, same treatment as
       .viewer-actions on the line above, rather than working out a
       fullscreen-safe position this sandbox can't visually confirm. */
    .viewer-meta-toggle{position:absolute;top:.65rem;left:.75rem;width:34px;height:34px;border:0;border-radius:50%;font-size:1.15rem;line-height:1;background:rgba(255,255,255,.92);color:#11215e;cursor:pointer;z-index:1}
    .viewer-meta-panel{position:absolute;top:3.1rem;left:.75rem;max-width:min(320px,80vw);padding:.85rem 1rem;border-radius:14px;background:rgba(7,19,60,.92);color:#fff;font-size:.92rem;box-shadow:0 12px 30px rgba(0,0,0,.35);z-index:1}
    .viewer-meta-panel:not(.is-open){display:none}
    .viewer-meta-panel dl{display:grid;grid-template-columns:auto 1fr;gap:.2rem .6rem;margin:0}
    .viewer-meta-panel dt{color:#b9c6f2;font-weight:600}
    .viewer-meta-panel dd{margin:0}
    .viewer-meta-panel .viewer-meta-keywords{margin:.5rem 0 0;color:#dce7ff;font-size:.85rem}
    .viewer-shell:fullscreen .viewer-meta-toggle,.viewer-shell:fullscreen .viewer-meta-panel{display:none!important}

    /* -- Path 3: dialog-fallback — no <dialog> support at all -- */
    .media-viewer.lw-dialog-fallback{display:block!important;position:fixed!important;z-index:9999!important;inset:0!important;width:100vw!important;height:100vh!important;height:100dvh!important;max-width:none!important;max-height:none!important;margin:0!important;padding:18px!important;border:0!important;background:#07133c!important;color:#fff!important;overflow:auto!important}
    .media-viewer.lw-dialog-fallback::backdrop{display:none!important}
    .lw-viewer-open{overflow:hidden!important}
    @media(max-width:850px){
      .media-viewer.lw-dialog-fallback{padding:max(12px,env(safe-area-inset-top)) max(12px,env(safe-area-inset-right)) max(12px,env(safe-area-inset-bottom)) max(12px,env(safe-area-inset-left))!important}
      .media-viewer.lw-dialog-fallback .viewer-shell{max-height:calc(100dvh - 24px)!important}
      .media-viewer.lw-dialog-fallback video,.media-viewer.lw-dialog-fallback img{width:100%!important;max-height:calc(100dvh - 150px)!important;object-fit:contain!important}
      .media-viewer.lw-dialog-fallback .viewer-actions{flex-direction:column!important;width:100%!important}
      .media-viewer.lw-dialog-fallback .viewer-actions button{width:100%!important;min-width:0!important}
    }

    /* -- Path 4: mobile overlay — narrow viewport or touch-enabled Apple UA -- */
    .lw-mobile-viewer-overlay{display:none;position:fixed;z-index:99999;inset:0;width:100vw;height:100dvh;min-height:100svh;padding:max(12px,env(safe-area-inset-top)) max(12px,env(safe-area-inset-right)) max(12px,env(safe-area-inset-bottom)) max(12px,env(safe-area-inset-left));background:#07133c;color:#fff;overflow:auto}
    .lw-mobile-viewer-overlay.is-open{display:block}
    .lw-mobile-viewer-overlay[hidden]{display:none!important}
    .lw-mobile-viewer-overlay .viewer-shell{max-height:calc(100dvh - 24px);display:flex;flex-direction:column}
    .lw-mobile-viewer-overlay [data-viewer-content]{flex:1;min-height:0;display:flex;flex-direction:column}
    .lw-mobile-viewer-overlay [data-viewer-content] video,.lw-mobile-viewer-overlay [data-viewer-content] img{width:100%!important;max-width:100%;max-height:calc(100dvh - 160px)!important;object-fit:contain;background:#000}
    .lw-mobile-viewer-overlay .viewer-actions{flex-direction:column!important;width:100%;margin-top:auto}
    .lw-mobile-viewer-overlay .viewer-actions button{width:100%!important;min-width:0!important}
    .lw-mobile-viewer-overlay .viewer-close{position:fixed!important;top:max(12px,env(safe-area-inset-top))!important;right:max(12px,env(safe-area-inset-right))!important;z-index:2}
    /* ---- End Round 18 consolidated viewer block ------------------------- */
    /* Round 18: fault 1 from live testing. The :has() wrap rule (removed
       below) worked — links dropped to a second row — but .site-header
       carries a locked height (64px at this width; see the "stronger
       public identity" rule further down), so that second row escaped
       the header's own box and painted transparently over page content
       beneath it: no background of its own, no stacking context above
       page content. Rather than let the header grow when the panel opens
       (reintroducing the layout shift the height lock exists to
       prevent), the panel is taken out of the header's flex row
       entirely and anchored below it with its own solid background,
       explicit stacking above the header's z-index:5, and its own
       scroll if the list is ever taller than the viewport. top:64px
       matches the header's own locked height at this breakpoint — if
       that height lock ever changes, this needs to change with it. */
    @media(max-width:850px){
      .site-header .nav-links.open{display:flex!important;position:fixed!important;top:64px!important;left:0!important;right:0!important;flex-direction:column!important;align-items:stretch!important;gap:.25rem!important;margin:0!important;width:100%!important;max-height:calc(100vh - 64px)!important;overflow-y:auto!important;padding:.65rem 1.1rem!important;background:var(--paper)!important;box-shadow:0 16px 32px rgba(8,19,61,.18)!important;border-bottom:1px solid var(--line)!important;z-index:6!important}
      .site-header .nav-links.open a{display:block!important;width:100%!important;font-size:18px!important;padding:.7rem .85rem!important}
    }
    /* Round 17 added a :has() rule here to force .nav to wrap when the
       panel opened, so the column-panel styling above could take effect.
       Round 18 takes the open panel out of the header's flex row
       entirely (position:fixed — see that rule's own comment, above), so
       nothing needs .nav to wrap for the menu's sake any more; removed
       rather than left in place doing nothing. .site-header .nav still
       carries flex-wrap:nowrap (added for the height lock, unrelated to
       the menu) further down, and that no longer matters to the menu
       either way. */
    /* A slightly stronger public identity, consistent on every page. */
    .site-header,.site-header .nav{height:72px!important;min-height:72px!important;max-height:72px!important}
    .site-header .brand{font-size:30px!important}.site-header .nav-links a{font-size:19px!important}
    @media(max-width:850px){.site-header,.site-header .nav{height:64px!important;min-height:64px!important;max-height:64px!important}.site-header .brand{font-size:24px!important}.site-header .nav-links a{font-size:16px!important}}
    @media(max-width:700px){.viewer-actions{flex-direction:column!important}.viewer-actions button{width:100%!important;min-width:0!important;flex:1 1 auto!important}}
    .album-grid{display:grid;grid-template-columns:repeat(3,minmax(0,1fr));gap:1rem}.album-card{display:block;overflow:hidden;border:1px solid var(--line);border-radius:24px;background:rgba(255,255,255,.9);box-shadow:var(--shadow);text-decoration:none;transition:transform .18s ease,box-shadow .18s ease}.album-card:hover{transform:translateY(-4px);box-shadow:0 20px 48px rgba(8,19,61,.2)}.album-cover-placeholder{display:block;width:100%;aspect-ratio:4/3;object-fit:cover;background:linear-gradient(140deg,#593217,#b56a35 60%,#f1b66d)}
.album-card img{display:block;width:100%;aspect-ratio:4/3;object-fit:cover;background:var(--ink)}.album-cover-placeholder{padding:1.25rem;color:white;font-size:1.4rem;font-weight:800;display:flex;align-items:end}.album-card>div{padding:1rem}.album-card h2{font-size:1.3rem}.album-card p{color:var(--muted);line-height:1.45}.album-card span{font-weight:800;color:var(--blue)}.empty-albums{grid-column:1/-1;color:var(--muted)}.album-navigation{max-width:1220px;margin:-2rem auto 2rem;padding:0 1.1rem;display:grid;grid-template-columns:1fr auto 1fr;gap:1rem;align-items:center}.album-navigation a{color:var(--blue);font-weight:800;text-decoration:none}.album-navigation a:last-child{text-align:right}.album-navigation a:nth-child(2){padding:.55rem 1rem;border-radius:999px;background:#e8ebff}.album-page-intro{display:flex;align-items:start;justify-content:space-between;gap:1rem}.album-page-intro .album-navigation{width:auto;min-width:380px;margin:.55rem 0 0;padding:0}.album-back{display:block;max-width:1220px;margin:-2rem auto 2rem;padding:0 1.1rem;color:var(--blue);font-weight:800;text-decoration:none}@media(max-width:850px){.album-grid{grid-template-columns:1fr}.album-page-intro{display:block}.album-page-intro .album-navigation{width:100%;min-width:0;margin:1rem 0 0}}
    /* Every public media card reserves one consistent action line. */
    .media-grid .media-card{display:flex;flex-direction:column;height:100%}.media-grid .media-card .card-body{display:flex;flex:1;flex-direction:column}.media-grid .media-card .card-open{align-self:flex-start;margin:0 1rem 1rem}.media-grid .media-card .card-body p:last-child{margin-bottom:1rem}

    /* ---- Testing round 2 additions -------------------------------------
       Bug 2: panoramas now open inside the same media-viewer dialog trail
       cam clips use (assets/js/experience.js), via a .pano-frame iframe,
       instead of navigating to the standalone package — so they need the
       same sizing rules video/img already get in every state that dialog
       can be in. */
    .media-viewer .pano-frame,.viewer-shell .pano-frame{width:100%;height:70vh;border:0;display:block;background:#000}
    /* .media-viewer:fullscreen .pano-frame removed — dead code, same as
       .media-viewer:fullscreen itself; see the Round 18 consolidated
       viewer block's comment further up for how this was confirmed. */
    .viewer-shell:fullscreen .pano-frame{width:100%!important;height:calc(100vh - 36px)!important}
    .media-viewer.lw-dialog-fallback .pano-frame{width:100%!important;height:calc(100dvh - 150px)!important}
    .lw-mobile-viewer-overlay [data-viewer-content] .pano-frame{width:100%!important;height:calc(100dvh - 160px)!important;background:#000}

    /* ---- Round 6: photo crossfade rebuilt as two permanent layers ------
       Rounds 3-5 inserted and removed a second <img> for the duration of
       each transition, and round 5 locked only the stage's HEIGHT in
       JavaScript, snapshotted from whatever the one image in flow at that
       moment happened to measure. Round 6 found the actual fault, by
       direct inspection of this file and experience.js rather than by
       further guessing about timing: with both images taken out of flow
       (position:absolute) for the duration of every transition,
       .photo-stage had no in-flow content left to derive a WIDTH from
       either — nothing here ever set one explicitly, only height was
       locked. In the ordinary player a block box still fills its
       container's width regardless of its children, which is why the
       height lock looked sufficient there; a photograph of a different
       aspect ratio, laid out afresh against a stage whose width was
       incidental rather than fixed, still produced the reported jump. In
       full screen, [data-viewer-content] is display:flex (below), so a
       stage with no in-flow children shrinks to zero width by default —
       both images render at zero size, which is the reported blink,
       until cleanup returned one child to flow and gave the box
       something to size itself from again. Both symptoms trace to one
       cause: the stage's own size was allowed to depend on children
       that, for the whole duration of every transition, contributed
       nothing to it.
       The fix is structural, not a tighter lock. .photo-stage now carries
       an explicit width and height in CSS, in every context this dialog
       can appear in — each rule below mirrors the width/max-height rule
       the single <img> used to carry in that same context, referenced in
       its own comment — so the stage's size never depends on its
       children at all: closed by construction, not measured and pinned
       by JavaScript on every transition. It holds exactly two
       <img class="photo-frame"> children, always, created once when a
       photograph is first shown on a given page and never inserted or
       removed again (see experience.js). Both are permanently
       position:absolute and fill the stage; only their opacity ever
       changes, animated by the one transition declaration below,
       unchanged from round 3. There is no is-crossfading class, because
       there is no state to enter or leave. */
    .photo-stage{position:relative;overflow:hidden;background:#000;width:100%;height:74vh}
    /* Round 7: duration now reads the site-wide setting (admin: Pages and
       sections → Photo slideshow), written per page by site_render.php's
       lw_render_head() as --photo-crossfade-duration on :root. The .3s
       fallback is what applies on any page generated before this setting
       existed, or if the custom property is ever absent for any other
       reason — never no transition at all. */
    .photo-stage .photo-frame{position:absolute;top:0;right:0;bottom:0;left:0;width:100%;height:100%;margin:0;object-fit:contain;object-position:center center;transform:none;opacity:0;transition:opacity var(--photo-crossfade-duration,.3s) ease}
    .photo-stage .photo-frame.is-visible{opacity:1}
    /* .media-viewer:fullscreen .photo-stage removed — this comment
       already correctly identified it as unused; round 18 removed the
       dead rule itself rather than keeping it "in step" with a selector
       that never matches. See the Round 18 consolidated viewer block's
       comment (further up) for how .media-viewer:fullscreen was
       confirmed dead rather than assumed. */
    /* Mirrors .viewer-shell:fullscreen video,.viewer-shell:fullscreen img
       above — the fullscreen path actually in effect. */
    .viewer-shell:fullscreen .photo-stage{width:100%!important;height:calc(100vh - 36px)!important}
    /* Mirrors .media-viewer.lw-dialog-fallback video,...img above. */
    .media-viewer.lw-dialog-fallback .photo-stage{width:100%!important;height:calc(100dvh - 150px)!important}
    /* Mirrors .lw-mobile-viewer-overlay [data-viewer-content] video,...img
       above. */
    .lw-mobile-viewer-overlay [data-viewer-content] .photo-stage{width:100%!important;height:calc(100dvh - 160px)!important;background:#000}

    /* Round 8: site.js's makeMultiSelect() (Subject/Tags/Camera) and its
       tag-toggle button had no rules at all — see round 8 deployment
       note. .tags now defaults to display:none above; shown only with
       body.show-card-tags. */
    .multi-filter{position:relative;margin-top:.3rem;width:100%}
    .multi-filter summary{list-style:none;cursor:pointer;width:100%;box-sizing:border-box;min-height:42px;padding:.6rem .7rem;border:1px solid var(--line);border-radius:12px;background:#fff;font:inherit;font-weight:400;color:var(--ink);display:flex;align-items:center}
    .multi-filter summary::-webkit-details-marker{display:none}
    .multi-filter summary::after{content:'\25be';margin-left:auto;color:var(--muted)}
    .multi-filter[open] summary::after{content:'\25b4'}
    .multi-filter-options{position:absolute;z-index:5;top:calc(100% + .3rem);left:0;right:0;max-height:16rem;overflow-y:auto;background:#fff;border:1px solid var(--line);border-radius:12px;padding:.5rem;box-shadow:0 8px 24px rgba(0,0,0,.12);display:flex;flex-direction:column;gap:.35rem}
    .multi-filter-options label{display:flex;align-items:center;gap:.5rem;font-weight:400;color:var(--ink);font-size:.9rem}
    /* Round 13: matched to .card-open's blue rather than the plain
       input/select treatment it had before. */
    /* Round 16: was min-height:42px + padding:.6rem .9rem — taller than
       any other control on the filter row and taller than the space its
       one line of text needs. Padding cut, min-height dropped to a value
       that still clears a comfortable tap target without matching (or
       exceeding) the row's other 42px controls, which is what was making
       it read as oversized among them. Colour and shape unchanged. */
    .tag-toggle{min-height:34px;padding:.3rem .8rem;border:0;border-radius:999px;background:var(--blue);font:inherit;font-weight:800;color:#fff;cursor:pointer}
    body.show-card-tags .tags{display:flex}
    /* Round 9: filtering itself was fine; nothing hid a card once
       .media-card[hidden] was set. .media-grid .media-card{display:flex}
       (see "Every public media card reserves one consistent action
       line") has higher specificity than the UA [hidden]{display:none}
       rule, so it always won. Confirmed by cascade math, not by watching
       it happen — see round 9 note. Fixed globally, not locally, below
       — the second time a display rule has quietly outbid intended
       behaviour in this file, after the round-4 fullscreen override:
       any element the JavaScript hides anywhere on the site must stay
       hidden regardless of how many classes its own display rule has. */
    [hidden]{display:none!important}
    /* input,select{width:100%;min-height:42px;...} (near the top of this
       file) was reaching the checkboxes inside the tag/subject/camera
       panel — same failure .photo-pick input already works around
       below. Reset to native checkbox sizing. */
    .multi-filter-options input[type=checkbox]{width:auto;min-height:0;margin:0;padding:0;border-radius:0}
    /* Round 11: chip click is a toggle — active state matches
       .button.primary's convention (solid blue, not the default light
       chip). */
    .tag.is-active{background:var(--blue);color:#fff}
/* Task 154, public comments (spec 6.9) — comments.html's list of
   approved comments, site_render.php's lw_render_comments_page().
   Deliberately its own small set of classes rather than reusing
   .about-card: an about-card is one editable block among a handful; a
   comment card is one of a potentially long, unbounded list, and needs
   its own tighter spacing rather than about-card's larger padding. */
.lw-comments-list{max-width:44rem;margin:2rem auto;display:flex;flex-direction:column;gap:1rem;padding:0 1.1rem}
.lw-comment-card{border:1px solid var(--line);box-shadow:var(--shadow);border-radius:16px;background:rgba(255,255,255,.9);padding:1.1rem 1.3rem}
.lw-comment-head{display:flex;align-items:baseline;justify-content:space-between;gap:1rem;margin-bottom:.4rem}
.lw-comment-date{color:var(--muted);font-size:.85rem}
.lw-comment-body{line-height:1.55;margin:0}
.lw-comment-reply{margin-top:.8rem;padding:.7rem 1rem;border-left:3px solid var(--blue);background:#f4f6ff;border-radius:0 10px 10px 0}
.lw-comment-reply p{margin:.3rem 0 0}
/* The comments form's "keep this private" tick — deliberately called
   out rather than styled like every other .contact-form label, since
   this is the one checkbox on the site where a visitor misreading it
   has a real consequence (a message meant to stay private, published).
   FIX: the round this shipped in claimed the round-10/11 checkbox reset
   (input[type=checkbox]{width:auto;...}) was site-wide — checked again
   after Simon's screenshot, and it never was: that rule is scoped to
   .multi-filter-options input[type=checkbox] only (confirmed by reading
   the actual selector, not re-grepping for a fragment of it, which is
   what produced the wrong reading the first time). Without any reset,
   this checkbox inherited the generic input,select{width:100%;
   min-height:42px} rule near the top of this file — an input stretched
   to the full width of a flex row, with flex-shrink:0 from the rule
   below, claims the whole row and squeezes the text into a sliver,
   which is exactly the huge-box-with-a-thin-text-column Simon saw. Now
   fixed here directly, not assumed inherited from elsewhere. */
.contact-form label.lw-private-tick{display:flex;align-items:flex-start;gap:.6rem;font-weight:600;color:var(--ink);background:#fff6e0;border:1px solid #f0c85a;border-radius:10px;padding:.6rem .8rem;margin-top:.6rem}
.contact-form label.lw-private-tick input[type=checkbox]{width:auto;min-height:0;min-width:0;margin:.15rem 0 0;padding:0;border-radius:0;flex:0 0 auto}
.contact-form label.lw-private-tick span{flex:1 1 auto;min-width:0}
/* Item 5: the feedback button, same line as the page heading. The
   eyebrow sits above this row (site_render.php's lw_render_collection_
   page_intro()), so the row itself contains only the <h1> and the
   button. align-items:center still looked low against a real
   screenshot — traced to the general h1{margin:.55rem 0 1.1rem} rule
   (near the top of this file): more margin below the text than above
   it, which skews flexbox's own alignment math even though the visible
   glyphs are centred. Zeroing that margin here, then bottom-aligning
   (align-items:flex-end), is what actually delivers "bottom of button
   level with bottom of heading" — literally, not approximately. Below
   700px — the same breakpoint .viewer-actions already uses for its own
   button row — they stack instead, button full width beneath the
   heading, rather than wrapping awkwardly inline next to it. */
.lw-page-intro-row{display:flex;align-items:flex-end;justify-content:space-between;gap:1.5rem;flex-wrap:wrap;margin:.55rem 0 1.1rem}
.lw-page-intro-row h1{margin:0}
.lw-feedback-cta{white-space:nowrap;padding-left:1.6rem;padding-right:1.6rem}
@media(max-width:700px){.lw-page-intro-row{flex-direction:column;align-items:stretch;gap:1rem}.lw-feedback-cta{width:100%;text-align:center}}
