/* img2ppt navbar — the MacBook-notch bar, shared by every public page.
   Lived inline in index.html until the legal and pricing pages needed it
   too; one copy here is the only way the notch geometry (which is a stack
   of three surfaces that have to line up to the pixel) stays identical
   across five files. Pair with nav.js, which owns the behaviour.

   Structure, top to bottom:
     body::before   the 4px gradient strip the bar hangs from
     header.bar     the notch itself, fixed and centred, width: fit-content
     .bar::before/  the two concave flares that make the notch's shoulders
     .bar::after    tangent to the strip instead of butting into it

   All three paint the same viewport-fixed gradient, so the seams between
   them are invisible -- and so overlapping them is free, which is how the
   shoulders absorb device-pixel snapping. Motion tokens come from theme.css.

   The notch geometry is scaled by --ui-scale as one piece: the flare arcs are
   cut to the bar's own bottom radius, so if that grows and these do not, the
   shoulders stop being tangent and the seam shows. Change one, change all. */

/* Strip extends 120px above the viewport so browsers that still bounce show a
   brand-gradient roof the notch stays attached to, not a white gap. */
body::before { content: ""; position: fixed; top: -120px; left: 0; right: 0;
  height: calc(120px + 4px * var(--ui-scale)); background: var(--grad); z-index: 50; }
/* No rubber-band at the top: the notch is fixed and would visibly detach from
   the viewport edge during elastic overscroll. */
html { overscroll-behavior-y: none; }

header.bar { position: fixed; left: 0; right: 0;
  margin: 0 auto; width: fit-content;
  z-index: 60; display: flex; align-items: center; gap: var(--bar-gap);
  --bar-gap: calc(20px * var(--ui-scale));
  padding: calc(5px * var(--ui-scale)) calc(24px * var(--ui-scale)) calc(7px * var(--ui-scale));
  border-radius: 0 0 var(--r-md) var(--r-md);
  background-image: var(--grad);
  background-attachment: fixed;
  top: 0;
  /* WARNING for anyone adding motion here later: never put a transform on
     this element or any ancestor of it. The notch only works because the
     strip, the bar and the two flares all sample one viewport-fixed
     gradient, and a transform in that chain makes background-attachment:
     fixed resolve against the transformed box instead of the viewport --
     which drags a full light-to-deep gradient across the notch's own ~400px
     and destroys the seam the whole design rests on. Chrome happens to keep
     it viewport-anchored (measured); Safari and Firefox are not reliably on
     that side of the spec. Move it on `top` if it ever has to move. */
  /* fit-content with left:0/right:0/margin:auto overflows rather than wraps,
     and a signed-in visitor on a 320px phone carries an email address in
     here: unbounded, the bar ran ~640px wide and pushed the burger clean off
     the screen, which is the one control that cannot go missing -- it is the
     only navigation at this width. Capped here, shrunk at the email below. */
  max-width: calc(100% - 16px * var(--ui-scale));
}

/* ---- the shoulders ----
   Two concave fillets that carry the strip's bottom edge round into the bar's
   side, so the notch reads as one surface rather than a block stuck under a
   line. Each is a box painting the same viewport-fixed gradient with a circle
   masked out of its inner corner.

   The mask is an SVG path, not a radial-gradient. A gradient cannot draw an
   edge: a hard stop rasterises as a staircase, so the arc carried a 0.75px
   feather to hide it -- and a feather is a fixed CSS length, so on a 2x or 3x
   screen it becomes a 1.5-2.5 device-pixel smear sitting next to the bar's
   crisp border-radius. Both ends of the arc went pale exactly where they had
   to meet the strip and the bar, and the shoulder read as chipped. An SVG
   shape is rasterised at device resolution with real antialiasing, so the arc
   is as sharp as the corner it joins.

   Geometry in the path's own units (1 unit = 1px * --ui-scale), left shoulder;
   the right one is its mirror:

     viewBox   13 x 14, its top 1 unit above the strip's bottom edge
     circle    centre (0, 13), r 12, cut out whole rather than as a quarter
               wedge -- below the tangent point the disc still has to keep the
               fillet clear of the bar's side
     tangents  y = 1, the strip's bottom edge; x = 12, the bar's side

   The two spare units are deliberate overlap, not slack: one above, painted
   over the strip, and one to the side, tucked under the bar. Every length in
   here is fractional at --ui-scale 1.1 (4.4, 13.2, 15.4) and each of the three
   surfaces snaps to device pixels on its own, so without the overlap a seam
   opens on a fraction of a pixel -- which is the other thing the feather was
   really hiding. All three paint one fixed gradient, so overlap costs nothing.

   r 12 is --r-md unscaled, written out by hand: see design-system.md 2.4. If
   that radius moves, both paths move with it. */
/* Held in custom properties only because the data URIs are unreadable inline.
   preserveAspectRatio="none" makes the shape fill the box exactly instead of
   letterboxing inside it; the box keeps the viewBox's 13:14 ratio, so nothing
   is stretched by it. #000 here is an alpha value, not a colour. */
header.bar {
  --flare-l: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 13 14' preserveAspectRatio='none'%3E%3Cpath fill='%23000' d='M0 0H13V14H12V13A12 12 0 0 0 0 1Z'/%3E%3C/svg%3E");
  --flare-r: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 13 14' preserveAspectRatio='none'%3E%3Cpath fill='%23000' d='M13 0H0V14H1V13A12 12 0 0 1 13 1Z'/%3E%3C/svg%3E");
}
header.bar::before, header.bar::after { content: ""; position: absolute;
  top: calc(3px * var(--ui-scale));
  width: calc(13px * var(--ui-scale)); height: calc(14px * var(--ui-scale));
  background-image: var(--grad);
  background-attachment: fixed;
  -webkit-mask-repeat: no-repeat; mask-repeat: no-repeat;
  -webkit-mask-size: 100% 100%; mask-size: 100% 100%; }
header.bar::before { left: calc(-12px * var(--ui-scale));
  -webkit-mask-image: var(--flare-l); mask-image: var(--flare-l); }
header.bar::after { right: calc(-12px * var(--ui-scale));
  -webkit-mask-image: var(--flare-r); mask-image: var(--flare-r); }

/* The bar itself is inert: no entrance animation, it does not hide itself on
   scroll, and it takes on no shadow when the page scrolls under it. It is the
   only navigation on five pages, and a saturated brand block on white already
   separates itself from the content without help. The motion that is left all
   answers something the reader just did. */

/* Which items give way when the cap bites. Everything that carries meaning at
   a glance, or that has to stay tappable, refuses to shrink; the email is the
   one thing in the bar that still reads with characters missing. */
.logo, .beta-badge, .bar-nav, .bar-burger, .auth-area .quota,
.acct-btn .chev { flex: 0 0 auto; }
.auth-area, .acct, .acct-btn { min-width: 0; }
.auth-area .who, .acct-email { display: block; min-width: 0;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }

/* ---- brand ---- */
/* Baseline (not centre) so the beta badge can sit on the wordmark's baseline;
   a baseline group needs both items in it, otherwise the lone baseline item
   falls back to flex-start. The logo is the tallest item in the bar, so
   opting it into the group doesn't move it. */
.logo { font-size: calc(15px * var(--ui-scale)); font-weight: 700;
  letter-spacing: calc(-.3px * var(--ui-scale));
  white-space: nowrap; color: var(--on-accent); align-self: baseline;
  text-decoration: none; }
.logo em { font-style: normal; color: inherit; }
/* A status note next to the logo rather than a second brand element. The
   trailing letter-space is trimmed off the right so the gap to the hairline
   stays even. The negative margin cancels the flex gap and re-spends 6px of
   it, so the badge stays tucked against the wordmark at any --bar-gap -- a
   hardcoded -14px silently butted the two together the moment the narrow
   layout tightened the gap to 14px. */
/* Was 9px at a .6-alpha tint -- 2.08:1, and below the 11px type floor.
   --fs-micro at full --on-accent puts it at 4.51:1 on the strip's #917306
   core, the same as every other label in the bar -- AA, since 2.1.5 closed
   on 2026-09-12 when the core deepened. FULL means full: an opacity here
   quietly re-dims it -- the .75 this carried composited to ~2.84:1 while
   the comment beside it claimed parity with the bar. */
.beta-badge { font-size: var(--fs-micro); font-weight: var(--fw-bold);
  line-height: 1;
  letter-spacing: .16em; margin-right: -.16em;
  color: var(--on-accent); align-self: baseline;
  margin-left: calc(6px * var(--ui-scale) - var(--bar-gap)); }

/* Both a <button> (the app page opens a sheet) and an <a> (every other page
   links to the app page's deep link) wear this, so the anchor's default
   underline has to be turned off and re-offered on hover.
   Heaviest thing in the bar after the wordmark, and heavier than the nav
   links: it is the one action here, and everything else is a destination. */
/* .as-link is the shared reset from theme.css (a <button> or <a> that reads as
   a link); the bar only adds its own white-on-strip treatment on top. */
header.bar .as-link { color: var(--on-accent); font-size: calc(12px * var(--ui-scale));
  font-weight: var(--fw-bold);
  text-decoration: none; align-self: baseline;
  transition: color var(--dur-fast) var(--ease-out); }
header.bar .as-link:hover {
  text-decoration: underline dotted rgba(255,255,255,.7);
  text-underline-offset: 3px; }
header.bar :focus-visible { outline-color: var(--on-accent); }

/* ---- nav links ---- */
/* Full white, not a translucent one: every step of alpha back is contrast we
   can't get any other way while the palette is fixed. White on --grad-deep
   (#917306) is 4.51:1 (4.5075) -- AA at this size at last, closing the gap
   design-system.md 2.1.5 carried since 2026-08-22 (the coral core was 4.04,
   the first retheme core #a07f07 was 3.80). The margin is under a hundredth:
   any alpha on a label, or any lightening of the core, reopens it. */
/* align-self: baseline, so the 12px links sit on the same baseline as the
   15px wordmark and the 11px badge instead of being optically centred against
   them. .bar-nav is itself a flex container, so its baseline comes from its
   first link -- which is what we want on the row. The bar's align-items stays
   `center` for anything that has no text to align (the burger). */
.bar-nav { display: flex; gap: calc(15px * var(--ui-scale)); align-self: baseline; }
/* .auth-area .quota rides along: the credit balance is a navigation label that
   happens to carry a number, and it sits in the same row at the same size, so
   giving it its own hover was two treatments for one thing. It keeps the
   button reset it needs further down; everything visual is here. */
.bar-nav a,
.auth-area .quota { position: relative; color: var(--on-accent);
  font-size: calc(12px * var(--ui-scale));
  font-weight: 500; text-decoration: none; white-space: nowrap; }
/* An underline that wipes in from the left rather than text-decoration
   appearing all at once. A pseudo, so nothing about the link's box changes
   and the row can't reflow on hover.
   clip-path, not scaleX: a scaled pseudo is promoted to its own composited
   layer and rasterised without the subpixel antialiasing it has at rest, so
   the line rendered noticeably thicker for the length of the animation and
   then snapped thin when it settled. A clip only ever masks -- the pixels it
   reveals are the same pixels the line ends on -- so the thickness is
   identical in every frame. Whole 1px height for the same reason: nothing
   fractional left to snap. */
.bar-nav a::after,
.auth-area .quota::after { content: ""; position: absolute; left: 0; right: 0;
  bottom: -4px; height: 1px; background: currentColor;
  clip-path: inset(0 100% 0 0);
  transition: clip-path .16s var(--ease-out), opacity .16s var(--ease-out); }
/* The page you are on keeps the underline, dimmed so it reads as a location
   marker and not as a stuck hover. */
.bar-nav a[aria-current="page"]::after { clip-path: inset(0); opacity: .55; }
/* Touch fires a false hover on tap and would leave the underline stranded. */
@media (hover: hover) and (pointer: fine) {
  .bar-nav a:hover::after,
  .auth-area .quota:hover::after { clip-path: inset(0); opacity: 1; }
}
.bar-nav a:focus-visible::after,
.auth-area .quota:focus-visible::after { clip-path: inset(0); opacity: 1; }

/* ---- identity ---- */
/* Full --on-accent, not the rgba(255,255,255,.92) this carried until
   2026-09-12: a .92 alpha composited these labels to 3.78:1 on the core --
   the same quiet re-dim the badge's opacity was, on the two signed-in
   labels. */
.auth-area { font-size: calc(12px * var(--ui-scale)); font-weight: 500; display: flex;
  align-items: baseline; gap: calc(14px * var(--ui-scale)); align-self: baseline;
  color: var(--on-accent); }
/* Hairline marking the brand off from the menu items. A pseudo rather than
   markup so it can't strand itself when auth-area is still empty (pre-render
   or a failed /api/me). */
.auth-area:not(:empty)::before { content: ""; width: 1px;
  height: calc(12px * var(--ui-scale));
  flex: 0 0 auto; background: rgba(255,255,255,.4); margin-left: -3px;
  /* No text of its own, so it cannot join the baseline group -- pin it to
     the cap height of the labels either side instead. */
  align-self: center; position: relative; top: -1px; }
/* Plain text, no chip: the bar is already a strong brand block and a bordered
   pill on top of it reads as a second UI layer. It is a <button> on the app
   page (it opens the buy sheet) and an <a> everywhere else (it deep-links to
   it), so all this does is strip the button chrome -- the type, the colour and
   the wipe-in underline all come from the .bar-nav a rule above, which is the
   point: in the row it reads as one more label. */
.auth-area .quota { background: none; border: 0; padding: 0; cursor: pointer;
  font-family: inherit; line-height: inherit; }
/* Off the app page there is no buy sheet to open, so the balance is a link to
   the deep link that opens it, and the email is a link home rather than a
   dropdown -- duplicating the account menu here would mean two copies of the
   same logic drifting apart. Sign out lives on the app page. */
.auth-area .who { color: var(--on-accent); font-weight: 600;
  text-decoration: none; white-space: nowrap; }
.auth-area .who:hover { text-decoration: underline dotted rgba(255,255,255,.7);
  text-underline-offset: 3px; }

/* ---- account menu (app page) ---- */
/* Everything that isn't the balance lives behind the email, so the bar stays
   two items wide however many account actions we add later. */
.acct { position: relative; display: flex; }
.acct-btn { display: inline-flex; align-items: center; gap: calc(5px * var(--ui-scale));
  background: none; border: 0; padding: 0; cursor: pointer;
  font: inherit; color: var(--on-accent); font-weight: 600; white-space: nowrap; }
.acct-btn .chev { transition: transform .15s var(--ease-out); opacity: .8; }
.acct-btn[aria-expanded="true"] .chev { transform: rotate(180deg); }
.acct-btn:hover .chev { opacity: 1; }
/* Anchored to the button's right edge: the bar is centred, so a left-anchored
   menu would hang past the viewport on a long email. */
.acct-menu { position: absolute; top: calc(100% + 10px * var(--ui-scale)); right: 0;
  min-width: calc(168px * var(--ui-scale)); padding: calc(5px * var(--ui-scale)); z-index: 70;
  background: var(--card); border: 1px solid var(--card-line);
  border-radius: var(--r-md); color: var(--ink);
  box-shadow: var(--sh-3);
  transform-origin: top right;
  animation: pop-in .16s var(--ease-out); }
.acct-menu.hide { display: none; }
.acct-menu button { display: block; width: 100%; text-align: left;
  background: none; border: 0; cursor: pointer; border-radius: var(--r-sm);
  padding: calc(8px * var(--ui-scale)) calc(10px * var(--ui-scale)); font: inherit;
  font-size: calc(13px * var(--ui-scale)); font-weight: 500;
  color: var(--body); transition: background .12s var(--ease-out),
  color .12s var(--ease-out); }
.acct-menu button:hover { background: var(--accent-soft);
  color: var(--accent-ink); }

@keyframes pop-in {
  from { opacity: 0; transform: scale(.96) translateY(-4px); }
  to   { opacity: 1; transform: none; }
}

/* ---- narrow screens: burger + menu ---- */
/* The bar is a fixed, centred, fit-content notch, so the link row can't wrap
   -- a second line would break the arc the flare pseudos draw against. Below
   726px (660 * --ui-scale) the links move into a sheet that drops out of the
   notch instead. Breakpoints cannot read the knob -- see theme.css.
   Before this existed they were simply hidden, which was survivable while
   every destination also sat further down the same page; with the bar on the
   legal pages too, hiding them would leave those pages with no navigation. */
.bar-burger { display: none; position: relative;
  width: calc(22px * var(--ui-scale)); height: calc(18px * var(--ui-scale));
  background: none; border: 0; padding: 0; cursor: pointer; }
.bar-burger span { position: absolute; left: calc(3px * var(--ui-scale)); right: calc(3px * var(--ui-scale));
  height: 1.6px;
  /* A 1px cap on a 2px burger bar, not a component corner. */
  border-radius: 1px; background: var(--on-accent);
  transition: transform .2s var(--ease-out); }
.bar-burger span:first-child { top: calc(6px * var(--ui-scale)); }
.bar-burger span:last-child { top: calc(11px * var(--ui-scale)); }
/* Into an X: each line slides to the middle and rotates. Transform only, so
   the two states are one interruptible motion rather than an icon swap. */
.bar-burger[aria-expanded="true"] span:first-child {
  transform: translateY(calc(2.5px * var(--ui-scale))) rotate(45deg); }
.bar-burger[aria-expanded="true"] span:last-child {
  transform: translateY(calc(-2.5px * var(--ui-scale))) rotate(-45deg); }

/* Centred under the notch, growing out of its bottom edge -- the sheet should
   look like it came out of the thing you tapped, which is what
   transform-origin: top centre buys. visibility (rather than display) is what
   lets the close animate: it flips at the end of the exit, not the start. */
.bar-menu { position: absolute; top: calc(100% + 8px * var(--ui-scale)); left: 50%; z-index: 70;
  min-width: calc(190px * var(--ui-scale)); padding: calc(6px * var(--ui-scale));
  background: var(--card); border: 1px solid var(--card-line);
  border-radius: var(--r-md);
  box-shadow: var(--sh-3);
  transform-origin: top center;
  transform: translateX(-50%) translateY(-6px) scale(.96);
  opacity: 0; visibility: hidden;
  /* Closing is quicker than opening: the user has already decided. */
  transition: opacity .14s var(--ease-out), transform .14s var(--ease-out),
    visibility 0s linear .14s; }
.bar-menu.open { opacity: 1; visibility: visible;
  transform: translateX(-50%) translateY(0) scale(1);
  transition: opacity .18s var(--ease-out), transform .18s var(--ease-out),
    visibility 0s; }
.bar-menu a { display: block; border-radius: var(--r-sm); padding: var(--sp-8) var(--sp-12);
  font-size: var(--fs-body); font-weight: var(--fw-medium); color: var(--body);
  text-decoration: none;
  transition: background var(--dur-fast) var(--ease-out),
    color var(--dur-fast) var(--ease-out); }
.bar-menu a:hover, .bar-menu a:focus-visible { background: var(--accent-soft);
  color: var(--accent-ink); }
.bar-menu a[aria-current="page"] { color: var(--accent-ink);
  background: var(--accent-soft); }

@media (max-width: 726px) {   /* 660 * --ui-scale */
  .bar-nav { display: none; }
  .bar-burger { display: block; }
  header.bar { --bar-gap: calc(14px * var(--ui-scale));
    padding-left: calc(18px * var(--ui-scale)); padding-right: calc(18px * var(--ui-scale)); }
}
@media (min-width: 727px) {   /* 661 * --ui-scale */
  /* Belt and braces: if the viewport widens while the sheet is open, nav.js
     closes it, but the panel must not be reachable in the meantime. */
  .bar-menu { display: none; }
  /* Half of design-system.md 2.1.5's AA guarantee (the gradient's 25-75%
     deep zone in theme.css is the other half): capped at 50% of the
     viewport, the centred bar can never outgrow the deep core, so a label
     -- including a signed-in email, which ellipsizes -- can never slide
     onto the light sweep, where white is illegible. At this breakpoint's
     floor the cap is 363px and the signed-out bar wants ~330, so nothing
     is squeezed; below the breakpoint the gradient flattens instead. Width
     only: no transform here, ever (see the warning at the top). */
  header.bar { max-width: min(calc(100% - 16px * var(--ui-scale)), 50%); }
}

/* ---- reduced motion ---- */
/* Fewer and gentler, not none: the underline and the sheet still change state
   so the interface stays legible, but nothing travels across the screen. The
   bar itself has no motion to drop. */
@media (prefers-reduced-motion: reduce) {
  .bar-nav a::after,
  .auth-area .quota::after { transition: none; }
  .bar-burger span { transition: none; }
  .acct-menu { animation: none; }
  .bar-menu { transform: translateX(-50%);
    transition: opacity .14s ease, visibility 0s linear .14s; }
  .bar-menu.open { transform: translateX(-50%);
    transition: opacity .14s ease, visibility 0s; }
}
