/* Self-hosted: nothing third-party is fetched on load, which is what makes a strict CSP possible.
   These are Google's own subsets pulled byte-for-byte from fonts.gstatic.com, so metrics are
   unchanged from the CDN build. Latin only — the site renders no glyph outside the range below
   (the paste hint's U+2318 is outside every Google subset and already fell back to system-ui).
   Both files are variable in wght, so one file per family serves every weight declared here. */
@font-face{font-family:'Bricolage Grotesque';font-style:normal;font-weight:500 700;font-stretch:100%;
    font-display:swap;src:url(/fonts/bricolage-grotesque-latin.woff2) format('woff2');
    unicode-range:U+0000-00FF,U+0131,U+0152-0153,U+02BB-02BC,U+02C6,U+02DA,U+02DC,U+0304,U+0308,U+0329,U+2000-206F,U+20AC,U+2122,U+2191,U+2193,U+2212,U+2215,U+FEFF,U+FFFD}
@font-face{font-family:'Inter Tight';font-style:normal;font-weight:400 600;
    font-display:swap;src:url(/fonts/inter-tight-latin.woff2) format('woff2');
    unicode-range:U+0000-00FF,U+0131,U+0152-0153,U+02BB-02BC,U+02C6,U+02DA,U+02DC,U+0304,U+0308,U+0329,U+2000-206F,U+20AC,U+2122,U+2191,U+2193,U+2212,U+2215,U+FEFF,U+FFFD}

:root{
    --void:#03061C; --ink:#DDE9FF; --mute:#7E93C4;
    --line:rgba(53,200,255,.15); --cyan:#35C8FF; --blue:#1560E8;
    --pad:clamp(18px,3.4vw,44px); --gap:clamp(20px,3vw,40px); --panel:330px;
    --stage-max:max(420px,calc(100svh - 300px));
    /* shell hugs stage+panel so the two columns stay adjacent instead of drifting apart */
    --shell:min(1200px,calc(var(--stage-max) + var(--panel) + var(--gap) + var(--pad)*2));
  }
  *{box-sizing:border-box}
  /* min-height, not height: a fixed height would swallow the phone action bar's bottom padding */
  html,body{margin:0;min-height:100%}
  body{background:var(--void);color:var(--ink);font-family:'Inter Tight',system-ui,sans-serif;
    font-size:15px;line-height:1.5;-webkit-font-smoothing:antialiased;overflow-x:hidden}
  body::before,body::after{content:'';position:fixed;inset:0;pointer-events:none;z-index:100}
  body::before{background:radial-gradient(120% 90% at 50% 32%,transparent 40%,rgba(1,2,10,.85) 100%)}
  body::after{opacity:.45;mix-blend-mode:overlay;background-repeat:repeat;background-size:180px 180px}
  a{color:inherit} button{font:inherit;color:inherit;border:0;background:none;cursor:pointer}

  header{display:flex;align-items:baseline;gap:14px;flex-wrap:wrap;padding:var(--pad) var(--pad) 0;
    max-width:var(--shell);margin-inline:auto}
  .mark{font-family:'Bricolage Grotesque',sans-serif;font-weight:700;font-size:clamp(26px,4.6vw,40px);
    letter-spacing:-.035em;line-height:1;margin:0;
    background:linear-gradient(96deg,#EAFBFF,var(--cyan) 55%,var(--blue));
    -webkit-background-clip:text;background-clip:text;color:transparent}
  .sub{color:var(--mute);font-size:13.5px}

  /* Three pages, one bar. `margin-left:auto` parks it at the end of the header's existing
     row, so on desktop it adds no height at all — and the phone rules below reorder rather
     than stack it, which is what keeps --chrome unchanged. */
  .menu{display:flex;gap:4px;margin-left:auto;align-self:center}
  .menu a{text-decoration:none;color:var(--mute);font-size:12.5px;line-height:1;padding:7px 11px;
    border-radius:999px;border:1px solid transparent;white-space:nowrap;
    transition:color .18s ease,background .18s ease,border-color .18s ease}
  .menu a:hover{color:var(--ink);background:rgba(53,200,255,.07)}
  .menu a[aria-current=page]{color:var(--ink);border-color:var(--line);background:rgba(53,200,255,.06)}
  .menu a:focus-visible{outline:2px solid var(--cyan);outline-offset:1px}

  main{padding:clamp(20px,3.5vw,38px) var(--pad) var(--pad);display:grid;gap:var(--gap);
    grid-template-columns:minmax(0,1fr) var(--panel);align-items:start;
    max-width:var(--shell);margin-inline:auto}
  @media (max-width:900px){main{grid-template-columns:minmax(0,1fr)}}

  .left{display:flex;flex-direction:column;gap:14px;width:100%;
    max-width:min(100%,var(--stage-max))}
  .stage{position:relative;aspect-ratio:1;width:100%;border-radius:3px;overflow:hidden;
    background:#050818;box-shadow:0 0 0 1px var(--line),0 30px 90px -40px rgba(21,96,232,.8)}
  .stage canvas{display:block;width:100%;height:100%;opacity:0;transition:opacity .7s ease}
  .stage.ready canvas{opacity:1}
  .stage img.orig{position:absolute;inset:0;width:100%;height:100%;object-fit:cover;opacity:0}
  /* the original sits above the render, clipped to everything left of the split */
  .stage.ready img.orig{opacity:1;clip-path:inset(0 calc(100% - var(--split,0%)) 0 0)}
  .stage.ready{touch-action:pan-y}   /* horizontal drag is ours, vertical scroll stays the page's */

  .split{position:absolute;inset:0;opacity:0;transition:opacity .35s ease;pointer-events:none}
  .stage.ready .split{opacity:1}
  .split-line{position:absolute;top:0;bottom:0;left:var(--split,0%);width:2px;margin-left:-1px;
    background:linear-gradient(180deg,transparent,var(--cyan) 14%,var(--cyan) 86%,transparent);
    box-shadow:0 0 20px rgba(53,200,255,.85);opacity:0;transition:opacity .25s ease}
  .stage.ready .split-line{opacity:1}
  /* clamped so the grip never hangs half-off the frame at either extreme */
  .split-grip{position:absolute;top:50%;left:clamp(18px,var(--split,0%),calc(100% - 18px));
    width:36px;height:36px;
    margin:-18px 0 0 -18px;border-radius:50%;pointer-events:auto;cursor:ew-resize;
    background:rgba(3,6,28,.55);-webkit-backdrop-filter:blur(7px);backdrop-filter:blur(7px);
    box-shadow:0 0 0 1px var(--cyan),0 0 26px rgba(53,200,255,.55);
    transition:transform .18s ease,box-shadow .18s ease}
  .split-grip::before{content:'';position:absolute;inset:0;border-radius:50%;
    background:radial-gradient(circle at 50% 50%,rgba(53,200,255,.35),transparent 68%)}
  .split-grip::after{content:'\003C\00A0\003E';position:absolute;inset:0;display:grid;place-content:center;
    font-size:12px;letter-spacing:-1px;color:var(--cyan);text-shadow:0 0 8px rgba(53,200,255,.9)}
  .split-grip:hover,.stage.dragging .split-grip{transform:scale(1.12);
    box-shadow:0 0 0 1px var(--cyan),0 0 38px rgba(53,200,255,.85)}
  .split-grip:focus-visible{outline:2px solid var(--cyan);outline-offset:3px}
  .split-tag{position:absolute;top:10px;left:10px;font-size:10.5px;letter-spacing:.14em;
    text-transform:uppercase;color:var(--cyan);padding:3px 7px;border-radius:2px;
    background:rgba(3,6,28,.6);box-shadow:0 0 0 1px var(--line);
    opacity:0;transition:opacity .25s ease}
  .stage.dragging .split-tag{opacity:1}
  /* sits over Riso and Halftone too, which are near-white, so it needs a solid backdrop */
  .split-hint{position:absolute;bottom:12px;left:50%;transform:translateX(-50%);white-space:nowrap;
    font-size:11.5px;color:var(--ink);padding:5px 12px;border-radius:99px;
    background:rgba(3,6,28,.82);-webkit-backdrop-filter:blur(8px);backdrop-filter:blur(8px);
    box-shadow:0 0 0 1px rgba(53,200,255,.35);
    animation:hintpulse 2.6s ease-in-out infinite}
  .split-hint[hidden]{display:none}
  @keyframes hintpulse{0%,100%{opacity:.45}50%{opacity:.9}}
  @media (prefers-reduced-motion:reduce){.split-hint{animation:none;opacity:.7}}

  .gallery{display:flex;flex-direction:column;gap:8px;margin-top:2px}
  .gallery[hidden]{display:none}
  .gal-head{display:flex;justify-content:space-between;align-items:baseline;font-size:12.5px;
    color:var(--mute)}
  .gal-head b{color:var(--ink);font-weight:600}
  .gal-strip{display:flex;gap:7px;overflow-x:auto;padding-bottom:2px;scrollbar-width:thin}
  .gal{flex:0 0 auto;width:56px;padding:0;border-radius:2px;overflow:hidden;position:relative;
    box-shadow:0 0 0 1px var(--line);transition:box-shadow .18s ease,transform .18s ease}
  .gal:hover{transform:translateY(-2px);box-shadow:0 0 0 1px var(--cyan),0 0 20px rgba(53,200,255,.4)}
  .gal img{display:block;width:100%;aspect-ratio:1;object-fit:cover}
  .gal small{position:absolute;left:0;right:0;bottom:0;font-size:9.5px;padding:2px 3px;
    background:linear-gradient(transparent,rgba(3,6,28,.9));color:var(--ink);
    white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
  .drop{position:absolute;inset:0;display:grid;place-content:center;justify-items:center;gap:10px;
    text-align:center;padding:24px;transition:background .2s}
  .stage.ready .drop{display:none}
  .drop .big{font-family:'Bricolage Grotesque',sans-serif;font-size:clamp(20px,3vw,27px);
    letter-spacing:-.02em;font-weight:500}
  .drop .hint{color:var(--mute);font-size:13px;max-width:30ch}
  .drop-link{font-size:12.5px;color:var(--cyan);text-decoration:none;margin-top:2px;
    border-bottom:1px solid rgba(53,200,255,.28);padding-bottom:1px}
  .drop-link:hover{border-bottom-color:var(--cyan)}
  .stage.over{outline:1px solid var(--cyan);outline-offset:-9px}
  .stage.over .drop{background:rgba(21,96,232,.12)}
  .orb{width:58px;height:58px;border-radius:50%;margin-bottom:4px;
    background:radial-gradient(circle at 50% 42%,#F2FEFF,var(--cyan) 38%,rgba(21,96,232,0) 72%)}
  .busy{position:absolute;left:0;right:0;bottom:0;height:2px;background:var(--cyan);
    transform:scaleX(0);transform-origin:left;transition:transform .25s}
  .stage[data-busy="1"] .busy{transform:scaleX(1)}

  .panel{display:flex;flex-direction:column;gap:24px;padding-top:4px}

  /* minmax(0,…) or the long labels widen their own column and shear the swatch row */
  .modes{display:grid;grid-template-columns:repeat(9,minmax(0,1fr));gap:7px}
  @media (max-width:900px){.modes{grid-template-columns:repeat(5,minmax(0,1fr))}}
  .mode{display:flex;flex-direction:column;gap:6px;padding:0;text-align:left}
  .mode .sw{aspect-ratio:1;width:100%;border-radius:2px;overflow:hidden;position:relative;
    box-shadow:0 0 0 1px rgba(125,150,200,.22);transition:box-shadow .15s}
  .mode canvas{display:block;width:100%;height:100%}
  .mode .ph{position:absolute;inset:0}
  .mode small{font-size:10.5px;color:var(--mute);transition:color .15s;letter-spacing:-.005em;
    white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
  .mode:hover .sw{box-shadow:0 0 0 1px rgba(53,200,255,.55)}
  .mode[aria-pressed="true"] .sw{box-shadow:0 0 0 2px var(--cyan)}
  .mode[aria-pressed="true"] small{color:var(--ink)}

  .ctl{display:flex;flex-direction:column;gap:9px}
  .ctl[hidden]{display:none}
  .ctl .row{display:flex;justify-content:space-between;align-items:baseline;font-size:13.5px}
  .ctl .row b{font-weight:500}
  .ctl .row span{color:var(--mute);font-variant-numeric:tabular-nums}
  input[type=range]{-webkit-appearance:none;appearance:none;width:100%;height:16px;background:none}
  input[type=range]::-webkit-slider-runnable-track{height:2px;background:rgba(125,150,200,.3)}
  input[type=range]::-moz-range-track{height:2px;background:rgba(125,150,200,.3)}
  input[type=range]::-webkit-slider-thumb{-webkit-appearance:none;width:13px;height:13px;border-radius:50%;
    background:var(--cyan);margin-top:-5.5px;box-shadow:0 0 12px rgba(53,200,255,.75)}
  input[type=range]::-moz-range-thumb{width:13px;height:13px;border:0;border-radius:50%;
    background:var(--cyan);box-shadow:0 0 12px rgba(53,200,255,.75)}
  input[type=range]:focus-visible{outline:1px solid var(--cyan);outline-offset:6px}

  .acts{display:flex;flex-direction:column;gap:9px}
  .btn{display:block;width:100%;text-align:center;padding:12px 16px;border-radius:2px;
    border:1px solid var(--line);transition:background .15s,border-color .15s}
  .btn:hover:not(:disabled){border-color:rgba(53,200,255,.5);background:rgba(21,96,232,.14)}
  .btn:disabled{opacity:.32;cursor:not-allowed}
  .btn.primary{background:linear-gradient(96deg,var(--blue),var(--cyan));color:#02040F;font-weight:600;
    border-color:transparent}
  .btn.primary:hover:not(:disabled){filter:brightness(1.1)}
  .note{color:var(--mute);font-size:12.5px} .note b{color:var(--ink);font-weight:500}
  .note a{color:var(--cyan);text-decoration:none;border-bottom:1px solid rgba(53,200,255,.28)}
  .note a:hover{border-bottom-color:var(--cyan)}
  /* A button that has to read as a link. The undo for a publish lives inside the note (app.js
     explains why it cannot live on a button), and it must not look like a fourth action. */
  .lnk{padding:0;font:inherit;color:var(--cyan);border-bottom:1px solid rgba(53,200,255,.28)}
  .lnk:hover:not(:disabled){border-bottom-color:var(--cyan)}
  .lnk:disabled{color:var(--mute);border-bottom-color:transparent;cursor:default}
  .lnk:focus-visible{outline:2px solid var(--cyan);outline-offset:2px}

  /* Shared by /gallery's style filter and /stats' range switch. Scrolls sideways on a phone
     instead of wrapping to three lines and pushing the content below it off screen — the same
     move the editor's mode strip makes. */
  .chips{display:flex;gap:7px;overflow-x:auto;padding-bottom:3px;
    scrollbar-width:none;overscroll-behavior-x:contain}
  .chips::-webkit-scrollbar{display:none}
  .chip{flex:0 0 auto;font-size:12.5px;line-height:1;padding:8px 13px;border-radius:999px;
    color:var(--mute);border:1px solid var(--line);background:rgba(53,200,255,.03);
    white-space:nowrap;transition:color .18s ease,border-color .18s ease,background .18s ease}
  .chip:hover{color:var(--ink);border-color:rgba(53,200,255,.4)}
  .chip[aria-pressed=true]{color:#02040F;font-weight:600;border-color:transparent;
    background:linear-gradient(96deg,var(--blue),var(--cyan))}
  .chip:focus-visible{outline:2px solid var(--cyan);outline-offset:2px}

  footer{padding:0 var(--pad) 40px;color:var(--mute);font-size:12.5px;display:flex;gap:16px;flex-wrap:wrap;
    max-width:var(--shell);margin-inline:auto}
  footer a{text-decoration:none;border-bottom:1px solid var(--line);padding-bottom:1px}
  footer a:hover{color:var(--cyan)}
  .sr{position:absolute;width:1px;height:1px;overflow:hidden;clip:rect(0 0 0 0)}
  @media (prefers-reduced-motion:reduce){*{transition:none!important}}

  /* ===================== phone: one screen, never scrolls =====================
     A photo app should not make you scroll to reach the shutter, so the page is
     clipped to 100svh and the stage absorbs whatever height is left over.
     --chrome is the sum of every other row; if you add one, add it there too or
     the stage grows past the fold. */
  @media (max-width:900px){
    body{height:100svh;overflow:hidden;padding-bottom:0;display:flex;flex-direction:column}
    /* auto inline margins beat stretch on a flex item, which would shrink these to max-content */
    header,main,footer{width:100%}
    body{--chrome:238px}                                  /* empty: header, strip, footer */
    body.shot{--chrome:372px}                             /* plus sliders, note, action bar */
    body.shot:has(.gallery:not([hidden])){--chrome:450px}

    header{padding-top:calc(env(safe-area-inset-top) + 14px);gap:5px}
    .mark{font-size:25px}
    .sub{font-size:12.5px;line-height:1.35}
    body.shot .sub{display:none}   /* once there is a photo on screen the pitch has done its job */

    /* The menu rides on the mark's row and the sub drops below it, so the header stays exactly
       two lines and costs the same 238px it did before there was a menu. Stacking the menu as
       its own row would have added ~31px to every --chrome figure below. Measured at 375px:
       mark 110px + menu 175px = 285px against 339px of inner width, so they fit on one line. */
    .menu{order:2;gap:2px}
    .menu a{font-size:11.5px;padding:6px 9px}
    .sub{order:3;flex-basis:100%}

    main{padding-top:14px;gap:13px}
    .left{margin-inline:auto;gap:11px}
    /* the one flexible box: square always, as wide as the leftover height allows */
    .stage{border-radius:14px;width:min(100%,calc(100svh - var(--chrome)));margin-inline:auto}

    /* with no photo in it the stage is a drop target, not a preview, so let it take the whole
       screen rather than leaving a square of void above the fold */
    body:not(.shot) main{flex:1;min-height:0;grid-template-rows:minmax(0,1fr);align-items:stretch}
    body:not(.shot) .left{min-height:0}
    body:not(.shot) .stage{flex:1;min-height:0;width:100%;aspect-ratio:auto}
    .drop{padding:18px}
    .drop .hint{font-size:12.5px}

    /* adjusting a photo you have not picked yet is noise, and the sliders cost 130px */
    body:not(.shot) .panel{display:none}
    .panel{gap:11px;padding-top:0}

    /* a strip you swipe, not nine tiles stacked three deep */
    .modes{display:flex;gap:9px;overflow-x:auto;scroll-snap-type:x proximity;
      padding-bottom:2px;scrollbar-width:none;overscroll-behavior-x:contain}
    .modes::-webkit-scrollbar{display:none}
    .mode{flex:0 0 58px;scroll-snap-align:center;gap:5px}
    .mode .sw{border-radius:9px}
    .mode small{font-size:9.5px;text-align:center}

    .gallery{gap:6px;margin-top:0}
    .gal-head{font-size:11px}
    .gal{width:46px;border-radius:7px}
    .gal small{font-size:8.5px}

    /* label, track and value share one line: stacking them costs the stage 60px */
    /* 82px clears "Ring spacing", the longest first-slider label; nowrap so a longer one
       overflows visibly instead of silently growing the row and pushing past the fold */
    .ctl{display:grid;grid-template-columns:82px minmax(0,1fr) 24px;align-items:center;gap:0 10px}
    .ctl .row{display:contents}
    .ctl .row b{grid-area:1/1;font-size:12.5px;white-space:nowrap}
    .ctl .row span{grid-area:1/3;font-size:12px;text-align:right}
    .ctl input[type=range]{grid-area:1/2;height:24px}
    input[type=range]::-webkit-slider-thumb{width:17px;height:17px;margin-top:-7.5px}
    input[type=range]::-moz-range-thumb{width:17px;height:17px}

    /* one row, so the bar costs 64px instead of 145 — and it stays one row now that Publish has
       joined it, which is the only reason every --chrome figure above is still correct. Measured
       at 375px: 339px inner, minus three 6px gaps and the 40px glyph, leaves 281px over 3.1fr —
       107 / 91 / 83. "Save to Photos", the longest label any of them takes, needs 95px of the
       107. A fifth button does not fit; it would have to become a second row and cost ~55px. */
    .acts{position:fixed;left:0;right:0;bottom:0;z-index:101;
      display:grid;grid-template-columns:1.18fr 1fr .92fr 40px;gap:6px;
      padding:10px var(--pad) calc(10px + env(safe-area-inset-bottom));
      background:rgba(3,6,28,.86);-webkit-backdrop-filter:blur(16px) saturate(1.4);
      backdrop-filter:blur(16px) saturate(1.4);box-shadow:0 -1px 0 var(--line)}
    .btn{border-radius:11px;padding:12px 6px;font-size:13px}
    /* "Use another photo" earns a glyph, not a quarter of the bar */
    #reset{font-size:0;padding:0}
    #reset::after{content:"+";font-size:23px;font-weight:400;line-height:1;color:var(--mute)}
    .note{margin:0;font-size:11.5px;line-height:1.35;
      display:-webkit-box;-webkit-box-orient:vertical;-webkit-line-clamp:2;overflow:hidden}

    footer{padding-bottom:0;gap:10px;font-size:11.5px}
    body.shot footer{display:none}
  }
  @media (max-width:400px){
    .mode{flex-basis:54px}
    .btn{font-size:12px}
    .acts{gap:5px}
  }
