:root {
  --ink: #23232e;
  --muted: #8a8a99;
  --line: #e6e6ee;
  --bg: #f7f7fb;
  --accent: #4C78A8;
  --accent-green: #34d399;
  /* Height of the editor's sticky <h2>. .rows-head sticks directly below it, so
     the two MUST agree — that's why this is one variable, not two literals. */
  --editor-head-h: 32px;
}
* { box-sizing: border-box; }
html, body { height: 100%; }
body {
  margin: 0;
  font-family: -apple-system, "Segoe UI", Roboto, Arial, sans-serif;
  color: var(--ink);
  background: var(--bg);
  /* split the page into two independently-scrolling panes: chart / editor */
  display: flex;
  flex-direction: column;
  overflow: hidden;
}

/* ---- top bar ---- */
.topbar {
  display: flex; justify-content: space-between; align-items: center;
  gap: 16px; padding: 12px 20px; background: #fff;
  border-bottom: 1px solid var(--line); position: sticky; top: 0; z-index: 20;
}
.topbar .title { display: flex; align-items: baseline; gap: 10px; }
.topbar h1 { font-size: 17px; margin: 0; }
.tag {
  font-size: 11px; color: var(--muted); border: 1px solid var(--line);
  border-radius: 999px; padding: 2px 8px; text-transform: uppercase; letter-spacing: .04em;
}
.actions { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; }
.viewmodes { display: inline-flex; border: 1px solid var(--line); border-radius: 8px; overflow: hidden; }
.viewmodes button {
  border: 0; background: #fff; padding: 6px 10px; font-size: 12px; cursor: pointer; color: var(--muted);
}
.viewmodes button.on { background: var(--accent); color: #fff; }

.btn {
  display: inline-block; text-decoration: none; cursor: pointer; font-size: 13px;
  padding: 7px 12px; border-radius: 8px; border: 1px solid var(--line);
  background: #fff; color: var(--ink);
}
.btn.primary { background: var(--accent); border-color: var(--accent); color: #fff; }
.btn.ghost { background: transparent; }
.btn:hover { filter: brightness(0.97); }
.import { margin: 0; }
.import input[type=file] { display: none; }
.billing-portal-form { margin: 0; }

.flash { margin: 10px 20px; padding: 8px 12px; background: #eef6ff; border: 1px solid #cfe3ff; border-radius: 8px; font-size: 13px; }
.flash.err { background: #fff0f0; border-color: #ffd4d4; }

/* ---- email-verification banner ----
   Informational (same blue treatment as .flash), not alarming — an unverified
   account is a normal pre-payment state, not an error. */
.verify-banner {
  display: flex; align-items: center; justify-content: space-between; gap: 12px;
  flex-wrap: wrap; margin: 10px 20px; padding: 10px 14px;
  background: #eef6ff; border: 1px solid #cfe3ff; border-radius: 8px; font-size: 13px;
}
.verify-banner form { margin: 0; }

/* ---- paywall / preview modal ----
   The only checkout surface in the app: the free-tier "Export" buttons open this to
   show a watermarked preview (or, for a locked Excel export / a failed rasterization,
   a message in place of the image) alongside the subscribe/buy-out CTAs. */
.modal {
  position: fixed; inset: 0; z-index: 100;
  display: flex; align-items: center; justify-content: center;
  background: rgba(35, 35, 46, .55); padding: 20px;
}
/* [hidden] must win over the flex `display` above, or the modal renders open —
   the classic “display overrides the UA hidden rule” bug. */
.modal[hidden] { display: none; }
body.modal-open { overflow: hidden; }

.modal-card {
  position: relative; background: #fff; border-radius: 14px;
  width: 100%; max-width: 560px; max-height: 90vh; overflow: auto;
  padding: 28px 24px 20px; box-shadow: 0 20px 60px rgba(0, 0, 0, .28);
}
.modal-close {
  position: absolute; top: 10px; right: 10px; width: 30px; height: 30px; padding: 0;
  border: 1px solid var(--line); border-radius: 50%; background: #fff;
  color: var(--muted); font-size: 16px; line-height: 1; cursor: pointer;
}
.modal-close:hover { background: var(--bg); }

#preview-img {
  display: block; width: 100%; max-width: 100%; height: auto;
  border: 1px solid var(--line); border-radius: 8px; margin: 0 0 14px;
}
.modal-message { margin: 0 0 14px; font-size: 13px; color: var(--muted); }

.upsell { font-size: 13px; }
.unlock-cta-form, .buyout-cta-form {
  display: flex; flex-direction: column; gap: 8px; margin: 14px 0 0;
  padding: 12px; border: 1px solid var(--line); border-radius: 10px;
}
.unlock-cta-form { background: #fafafe; }
/* Buy-out is the secondary path — subscribe is primary — so it gets a visually
   quieter treatment (no fill, slightly muted) alongside its already-ghost button. */
.buyout-cta-form { background: transparent; opacity: .8; }
.unlock-cta-form .hint, .buyout-cta-form .hint { margin: 0; }

/* ---- gantt ---- */
/* top pane: fixed-ish height, scrolls in BOTH axes, drag the bottom edge to resize */
.gantt-wrap {
  flex: 0 0 auto; height: auto; min-height: 160px; max-height: 72vh;
  resize: vertical; overflow: hidden;       /* frame + resize only; the inner container scrolls */
  display: flex; flex-direction: column;
  margin: 12px 20px 8px; background: #fff; border: 1px solid var(--line);
  border-radius: 12px; padding: 10px;
}
#gantt { flex: 1 1 auto; min-height: 0; display: flex; flex-direction: column; }
/* Frappe's own scroll box fills the resizable frame and owns BOTH scrollbars, so the
   horizontal bar sits at the frame's bottom edge and stays visible when the frame is
   shrunk — instead of scrolling out of view at the bottom of the (taller) content. */
#gantt .gantt-container { flex: 1 1 auto; min-height: 0; max-height: 100%; }
/* Completion visual (matches the PDF): the incomplete remainder is a 25% tint of
   the section colour with a thin solid outline; Frappe's bar-progress paints the
   completed left portion solid in the section colour. */
.gantt .bar-wrapper .bar { fill-opacity: .25 !important; stroke-width: 1.3px !important; transition: none !important; }
.gantt .bar-wrapper.bar-milestone .bar { fill: #6b6b7a !important; stroke: #6b6b7a !important; }
.gantt .bar-wrapper.bar-milestone .bar-progress { fill: #6b6b7a !important; }
.gantt .bar-wrapper.bar-sec-0 .bar { fill: #4C78A8 !important; stroke: #4C78A8 !important; }
.gantt .bar-wrapper.bar-sec-0 .bar-progress { fill: #4C78A8 !important; }
.gantt .bar-wrapper.bar-sec-1 .bar { fill: #F58518 !important; stroke: #F58518 !important; }
.gantt .bar-wrapper.bar-sec-1 .bar-progress { fill: #F58518 !important; }
.gantt .bar-wrapper.bar-sec-2 .bar { fill: #54A24B !important; stroke: #54A24B !important; }
.gantt .bar-wrapper.bar-sec-2 .bar-progress { fill: #54A24B !important; }
.gantt .bar-wrapper.bar-sec-3 .bar { fill: #B279A2 !important; stroke: #B279A2 !important; }
.gantt .bar-wrapper.bar-sec-3 .bar-progress { fill: #B279A2 !important; }
.gantt .bar-wrapper.bar-sec-4 .bar { fill: #E45756 !important; stroke: #E45756 !important; }
.gantt .bar-wrapper.bar-sec-4 .bar-progress { fill: #E45756 !important; }
.gantt .bar-wrapper.bar-sec-5 .bar { fill: #72B7B2 !important; stroke: #72B7B2 !important; }
.gantt .bar-wrapper.bar-sec-5 .bar-progress { fill: #72B7B2 !important; }
.gantt .bar-wrapper.bar-sec-6 .bar { fill: #EECA3B !important; stroke: #EECA3B !important; }
.gantt .bar-wrapper.bar-sec-6 .bar-progress { fill: #EECA3B !important; }
.gantt .bar-wrapper.bar-sec-7 .bar { fill: #9D755D !important; stroke: #9D755D !important; }
.gantt .bar-wrapper.bar-sec-7 .bar-progress { fill: #9D755D !important; }

/* Bar label contrast: white over the solid (completed) portion, dark over the 25%
   tint — chosen by whichever the centred label mostly sits on (set in JS). Labels
   Frappe pushes past the bar end (.big) stay dark on the plain background. */
.gantt .bar-wrapper.lbl-light .bar-label:not(.big) { fill: #ffffff !important; }
.gantt .bar-wrapper.lbl-dark .bar-label:not(.big) { fill: #23232e !important; }
.gantt .bar-label.big { fill: #23232e !important; }

/* ---- editor rows ----
   The old <table> aligned columns across sections for free. These blocks don't,
   so every row declares the SAME grid template — that shared declaration is what
   keeps rows in different sections lined up. Change it in one place only. */
.rows-head, .item {
  display: grid;
  grid-template-columns: 22px minmax(160px, 1fr) 132px 62px 150px 44px 150px 30px;
  gap: 8px;
  align-items: center;
}
.rows-head {
  padding: 8px 10px; font-size: 11px; text-transform: uppercase; letter-spacing: .04em;
  color: var(--muted); background: #fafafe; border: 1px solid var(--line);
  border-radius: 12px 12px 0 0; position: sticky; top: var(--editor-head-h); z-index: 2;
}
#sections { border: 1px solid var(--line); border-top: 0; border-radius: 0 0 12px 12px;
            background: #fff; overflow: hidden; }
.sec + .sec { border-top: 1px solid var(--line); }
.sec-head {
  display: flex; align-items: center; gap: 8px;
  padding: 7px 10px; background: #dcdce7; font-weight: 700;
}
.sec-name { font-size: 13px; }
.items { list-style: none; margin: 0; padding: 0; }
.item { padding: 5px 10px; font-size: 13px; }
.item + .desc-row { border-bottom: 1px solid var(--line); }
.grip { color: var(--muted); cursor: default; user-select: none; text-align: center; }
.sec .add { margin: 4px 10px 8px; }

/* ---- change-log table (audit.html; unrelated to the editor's grid rows) ---- */
table.rows { width: 100%; border-collapse: collapse; background: #fff; border: 1px solid var(--line); border-radius: 12px; overflow: hidden; }
table.rows th { text-align: left; font-size: 11px; text-transform: uppercase; letter-spacing: .04em; color: var(--muted); padding: 8px 10px; background: #fafafe; border-bottom: 1px solid var(--line); position: sticky; top: 0; z-index: 2; }
table.rows td { padding: 5px 10px; border-bottom: 1px solid var(--line); font-size: 13px; vertical-align: middle; }
.muted { color: var(--muted); }

/* bottom pane: fills the rest of the viewport and scrolls independently */
.editor { flex: 1 1 0; min-height: 0; overflow: auto; margin: 4px 20px 16px; }
/* Pinned to the top of the editor pane so the boundary with the chart stays
   legible while scrolling. The hint below it scrolls away underneath; .rows-head
   then parks against its bottom edge (see --editor-head-h). Opaque background is
   load-bearing — without it the rows scroll through the heading. */
.editor h2 {
  font-size: 15px; line-height: 18px; margin: 0; padding: 8px 0 6px;
  height: var(--editor-head-h);
  position: sticky; top: 0; z-index: 6; background: var(--bg);
}
.hint { font-size: 12.5px; color: var(--muted); margin: 0 0 10px; }
button.add { background: none; border: 1px dashed var(--line); color: var(--muted); border-radius: 8px; padding: 4px 10px; font-size: 12px; cursor: pointer; }
button.del { background: none; border: 0; color: #c0526a; cursor: pointer; font-size: 13px; }
.center { text-align: center; }
.sec-edit { background: none; border: 1px dashed var(--line); color: var(--muted);
            border-radius: 8px; padding: 2px 8px; font-size: 11px; cursor: pointer; }
.f-sec-name { font: inherit; font-size: 13px; padding: 2px 6px;
              border: 1px solid var(--accent); border-radius: 6px; }
.add-sec { display: block; margin: 8px 0 0; }
.f-name, .f-start, .f-dur {
  width: 100%; border: 1px solid transparent; border-radius: 6px; padding: 5px 6px; font: inherit; background: transparent;
}
.f-dur { max-width: 70px; }
select.f-dep { max-width: 220px; min-width: 0; border: 1px solid var(--line); border-radius: 6px; padding: 5px 6px; font: inherit; background: #fff; }
select.f-dep:focus { border-color: var(--accent); outline: none; }
.pct-cell { white-space: nowrap; }
select.f-pct-mode { border: 1px solid var(--line); border-radius: 6px; padding: 4px 6px; font: inherit; background: #fff; }
select.f-pct-mode:focus { border-color: var(--accent); outline: none; }
.f-pct-num { width: 56px; margin-left: 6px; border: 1px solid var(--line); border-radius: 6px; padding: 4px 6px; font: inherit; background: #fff; }
.f-pct-num[hidden] { display: none; }
.f-name:focus, .f-start:focus, .f-dur:focus, .f-pct-num:focus { border-color: var(--accent); background: #fff; outline: none; }

/* ---- descriptions ----
   ~0.82 of the row's 13px, indented 1em — mirrors the print SVG's ratio so the
   editor and the PDF read alike. See gantt.py's DESC_FONT.
   Single indent mechanism: .desc-edit's own left padding does all the work, so the
   textarea lands under the item's name column. The .item grid is
   22px minmax(160px,1fr) ... with gap 8px and padding 5px 10px, so the name
   column starts at 10 + 22 + 8 = 40px from the block's left edge; add ~1em
   at the row's 13px font (13px) for 53px total. (.sec-desc-edit keeps its own
   margin-left: 1em below — section descriptions have no equivalent column to
   line up under.)
   The padding lives on .desc-edit, NOT .desc-row: when a row has no visible
   description, .desc-edit is [hidden] (display:none) and .desc-row collapses to
   an empty <li> with no reserved height — but it still paints the separator
   border below (`.item + .desc-row`), since that belongs to the row above it. */
.desc-edit { padding: 0 10px 6px 53px; }
.desc-row textarea, .sec-desc-edit textarea {
  width: 100%; padding: 6px 8px; font: inherit; font-size: 10.5px;
  color: #6b6b7a; border: 1px solid var(--line); border-radius: 8px;
  background: #fafafe; resize: vertical;
}
.desc-row textarea:focus, .sec-desc-edit textarea:focus {
  border-color: var(--accent); background: #fff; outline: none;
}
.desc-hide, .sec-desc-toggle, .sec-desc-hide {
  background: none; border: 1px dashed var(--line); color: var(--muted);
  border-radius: 8px; padding: 2px 8px; font-size: 11px; cursor: pointer; margin-left: 1em;
}
.desc-edit, .sec-desc-edit { display: flex; align-items: flex-start; gap: 6px; }
.desc-edit[hidden], .sec-desc-edit[hidden] { display: none; }
.sec-desc-edit textarea { font-size: 11px; margin-left: 1em; }   /* ~0.85 of the heading, mirrors SEC_DESC_FONT/heading in the SVG */

/* ---- item row: hover-revealed "+ Add Desc" (replaces a permanent row of dead
   space per item). .name-cell wraps the name input so a small "+" can sit at its
   right edge; it is the SINGLE grid child in the name track (an absolutely
   positioned descendant doesn't count as a grid item, so nesting the buttons
   inside it doesn't add a 9th track). The pill (.desc-toggle) is a further
   descendant, positioned at the name-cell's own bottom edge with NO gap — CSS
   :hover matches an ancestor while the pointer is over any descendant, wherever
   that descendant is actually drawn, so touching (not gapped) keeps `.item:hover`
   true as the pointer travels straight down from the row onto the pill. */
.name-cell { position: relative; min-width: 0; }
.desc-plus, .desc-toggle {
  position: absolute; opacity: 0; pointer-events: none; cursor: pointer;
  transition: opacity .15s ease;
}
.item:hover .desc-plus, .item:hover .desc-toggle { opacity: 1; pointer-events: auto; }
.desc-plus {
  top: 50%; right: 2px; transform: translateY(-50%);
  width: 18px; height: 18px; padding: 0; line-height: 16px; text-align: center;
  border: 1px solid var(--line); border-radius: 50%; background: #fff;
  color: var(--muted); font-size: 13px;
}
.desc-toggle {
  top: 100%; left: 0; z-index: 4;   /* above .rows-head's sticky z-index: 2 */
  background: #fff; border: 1px solid var(--line); color: var(--muted);
  border-radius: 8px; padding: 2px 8px; font-size: 11px; white-space: nowrap;
  box-shadow: 0 2px 6px rgba(0, 0, 0, .14);
}

/* chart tooltip */
.popup-wrapper .popup-desc {
  margin: 6px 0 0; font-size: 11px; line-height: 1.4; opacity: .85;
  max-width: 260px; white-space: pre-wrap;
}

/* ---- login ---- */
.login-body { display: grid; place-items: center; min-height: 100vh; }
.login-card { background: #fff; border: 1px solid var(--line); border-radius: 14px; padding: 28px; width: 320px; box-shadow: 0 10px 30px rgba(0,0,0,.06); }
.login-card h1 { font-size: 20px; margin: 0 0 4px; }
.login-card .sub { color: var(--muted); font-size: 13px; margin: 0 0 16px; }
.login-card label { display: block; font-size: 12px; color: var(--muted); margin-bottom: 12px; }
.login-card input { width: 100%; margin-top: 4px; padding: 9px 10px; border: 1px solid var(--line); border-radius: 8px; font: inherit; }
.login-card button { width: 100%; margin-top: 6px; padding: 10px; border: 0; border-radius: 8px; background: var(--accent); color: #fff; font-size: 14px; cursor: pointer; }

.undogroup { display: inline-flex; gap: 4px; }
.btn:disabled { opacity: .4; cursor: default; pointer-events: none; }

.divider {
  margin: 14px 0 0; padding: 6px 10px; font-size: 11px; color: var(--muted);
  text-transform: uppercase; letter-spacing: .04em;
  border-top: 2px dashed var(--line);
}
#hidden-sections {
  min-height: 44px; border: 1px dashed var(--line); border-radius: 12px;
  background: #fafafe;
}
#hidden-sections .sec-head { background: #e8e8ef; }
#hidden-sections .sec { opacity: .72; }
.grip { cursor: grab; }
.grip:active { cursor: grabbing; }
/* Touch/forced-fallback drags only: the sticky editor chrome tops out at z-index 6
   (the pinned <h2>; .rows-head is 2), and without this
   a fallback-rendered drag element would pass under it. Native desktop HTML5 drag never
   creates a .sortable-drag element (the browser paints the drag image itself, already
   above everything), and .sortable-ghost is position:static, so z-index is a no-op there
   — this rule has nothing to do on the desktop path. */
.sortable-drag, .sortable-ghost { z-index: 7; }
.sortable-ghost { opacity: .4; }
.stale-banner { position: fixed; top: 0; left: 0; right: 0; z-index: 1000;
  background: #b45309; color: #fff; padding: 8px 14px; text-align: center; font-size: 14px; }
.stale-banner button { margin-left: 10px; }
