/* ==========================================================================
   آکس‌گو — the workshop app.

   One stylesheet, no framework. The main site's chrome (Bootstrap, Swiper,
   FontAwesome, jQuery) is ~400 KB before a single pixel of this app is drawn
   and none of it fits a phone held in a workshop, so /go dresses itself.

   The visual language comes from the material and the trade, not from a UI kit:
   ink is an oiled workbench, the light surface is a raw melamine face, borders
   are the colour of a sawn particle edge, and the one saturated colour is the
   AUX red, spent only on the primary action and on the number that decides the
   job. Corner radius carries meaning -- a card describing a physical panel is
   cut square with an edge band down its leading side; a card holding controls
   is rounded. Do not flatten that distinction into one radius.
   ========================================================================== */

/* --------------------------------------------------------------------------
   1. Tokens
   -------------------------------------------------------------------------- */

:root {
    color-scheme: light dark;

    /* Material palette. Names are the thing they came from. */
    --ink: #17130f;          /* oiled workbench */
    --ink-2: #211b16;
    --ink-3: #2e2620;
    --board: #f6f1e8;        /* raw melamine face, under shop light */
    --board-2: #ece4d6;
    --board-3: #ded3c0;
    --kerf: #b8a184;         /* the sawn particle edge */
    --kerf-soft: #d6c8b2;
    --aux: #ed1c24;          /* brand red — primary action, decisive number */
    --aux-dim: #c2151c;
    --steel: #55606b;        /* tooling grey-blue — secondary structure */
    --sap: #1e7f5c;          /* fits, saved, within tolerance */
    --amber: #b8730f;        /* check this */

    /* Semantic surfaces, light. Redefined wholesale for dark below. */
    --bg: var(--board);
    --bg-sunken: #ebe4d7;
    --surface: #fffdf9;
    --surface-2: #f4eee3;
    --line: #ddd2bd;
    --line-strong: #c3b195;
    --text: #221c16;
    --text-2: #6a5f52;
    --text-3: #93877a;
    --accent: var(--aux);
    --accent-ink: #ffffff;
    --ok: var(--sap);
    --warn: var(--amber);
    --shadow-lift: 0 8px 28px -12px rgba(35, 25, 15, .45);
    --shadow-sheet: 0 -16px 48px -18px rgba(35, 25, 15, .55);

    /* The cut plan draws with these in both themes. */
    --panel-fill: #e3d5bb;
    --panel-fill-2: #d8c8aa;
    --panel-stroke: #8d7a5e;
    --panel-text: #3a2f22;
    --offcut: rgba(85, 96, 107, .16);

    /* Type scale. Vazirmatn is already self-hosted for the main site. */
    --font: 'Vazirmatn', system-ui, -apple-system, 'Segoe UI', sans-serif;
    --t-display: 2rem;
    --t-title: 1.3125rem;
    --t-head: 1.0625rem;
    --t-body: 0.9375rem;
    --t-small: 0.8125rem;
    --t-micro: 0.6875rem;

    /* Rhythm */
    --gap: 0.75rem;
    --pad: 1rem;
    --r-control: 14px;   /* controls, sheets, inputs */
    --r-panel: 3px;      /* anything standing for a cut panel */
    --r-pill: 999px;
    --tap: 44px;

    /* Chrome heights, read by JS for scroll offsets. */
    --bar-top: 56px;
    --bar-bottom: 60px;
    --safe-top: env(safe-area-inset-top, 0px);
    --safe-bottom: env(safe-area-inset-bottom, 0px);
}

@media (prefers-color-scheme: dark) {
    :root:not([data-theme='light']) {
        --bg: var(--ink);
        --bg-sunken: #100d0a;
        --surface: var(--ink-2);
        --surface-2: var(--ink-3);
        --line: #362d25;
        --line-strong: #4d4136;
        --text: #f2ece2;
        --text-2: #b3a695;
        --text-3: #837768;
        --accent: #ff3d44;
        --accent-ink: #1a0c0c;
        --ok: #3fbf8f;
        --warn: #e0a03a;
        --shadow-lift: 0 8px 28px -12px rgba(0, 0, 0, .7);
        --shadow-sheet: 0 -16px 48px -18px rgba(0, 0, 0, .8);
        --panel-fill: #4a3f31;
        --panel-fill-2: #3c3328;
        --panel-stroke: #8a7a63;
        --panel-text: #ede4d5;
        --offcut: rgba(179, 166, 149, .12);
    }
}

:root[data-theme='dark'] {
    --bg: var(--ink);
    --bg-sunken: #100d0a;
    --surface: var(--ink-2);
    --surface-2: var(--ink-3);
    --line: #362d25;
    --line-strong: #4d4136;
    --text: #f2ece2;
    --text-2: #b3a695;
    --text-3: #837768;
    --accent: #ff3d44;
    --accent-ink: #1a0c0c;
    --ok: #3fbf8f;
    --warn: #e0a03a;
    --shadow-lift: 0 8px 28px -12px rgba(0, 0, 0, .7);
    --shadow-sheet: 0 -16px 48px -18px rgba(0, 0, 0, .8);
    --panel-fill: #4a3f31;
    --panel-fill-2: #3c3328;
    --panel-stroke: #8a7a63;
    --panel-text: #ede4d5;
    --offcut: rgba(179, 166, 149, .12);
}

/* --------------------------------------------------------------------------
   2. Reset and base
   -------------------------------------------------------------------------- */

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

html {
    -webkit-text-size-adjust: 100%;
    text-size-adjust: 100%;
}

body.go {
    margin: 0;
    font-family: var(--font);
    font-size: var(--t-body);
    line-height: 1.7;
    color: var(--text);
    background: var(--bg);
    /* An installed app should not rubber-band like a document. */
    overscroll-behavior-y: none;
    -webkit-tap-highlight-color: transparent;
}

html.standalone body.go {
    /* Nothing outside the app can be scrolled to in standalone mode. */
    overflow: hidden;
    height: 100dvh;
}

h1, h2, h3, h4 { margin: 0; font-weight: 700; line-height: 1.4; }
p { margin: 0 0 .75em; }
ul, ol { margin: 0; padding-inline-start: 1.25em; }
a { color: inherit; text-decoration: none; }
img, svg, canvas { max-width: 100%; }
button, input, select, textarea { font: inherit; color: inherit; }

/* The UA's [hidden] rule matches at element specificity and loses to any class
   that sets display -- .go-iconbtn, .btn, .listrow all do. Toggling `hidden`
   is how this app hides the back button and the update bar, so it has to win. */
[hidden] { display: none !important; }

:focus-visible {
    outline: 2px solid var(--accent);
    outline-offset: 2px;
    border-radius: 4px;
}

.go-sr {
    position: absolute;
    width: 1px; height: 1px;
    padding: 0; margin: -1px;
    overflow: hidden;
    clip-path: inset(50%);
    white-space: nowrap;
}

/* Measurements are read off a tape measure and typed into a CNC, so they are
   set in Latin digits, tabular, and slightly tight. Persian digits belong in
   prose -- see format.js, which decides which of the two a value gets. */
.num {
    font-variant-numeric: tabular-nums;
    font-feature-settings: 'tnum' 1, 'lnum' 1;
    letter-spacing: -0.01em;
    direction: ltr;
    unicode-bidi: isolate;
    display: inline-block;
}

/* --------------------------------------------------------------------------
   3. Shell: top bar, scroll region, tab bar
   -------------------------------------------------------------------------- */

.go-shell {
    display: grid;
    /* minmax(0, 1fr), not the default `auto`. An auto column sizes to the
       max-content of its widest child, so a card whose padding is in rem grows
       the column past the viewport once the reader enlarges text -- measured at
       160%: a 360 px phone got a 411.6 px column and the whole app, top bar
       included, scrolled sideways. Same family as the minmax(min(...), 100%)
       fix in the card grids below. */
    grid-template-columns: minmax(0, 1fr);
    grid-template-rows: auto 1fr auto;
    min-height: 100dvh;
}

html.standalone .go-shell { height: 100dvh; }

.go-topbar {
    position: sticky;
    top: 0;
    z-index: 40;
    display: flex;
    align-items: center;
    gap: .5rem;
    min-height: var(--bar-top);
    padding: var(--safe-top) .75rem 0;
    background: color-mix(in srgb, var(--bg) 88%, transparent);
    backdrop-filter: saturate(1.4) blur(14px);
    border-bottom: 1px solid var(--line);
}

.go-topbar__title {
    flex: 1;
    min-width: 0;
    font-size: var(--t-head);
    font-weight: 700;
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
}

.go-topbar__sub {
    display: block;
    font-size: var(--t-micro);
    font-weight: 500;
    color: var(--text-3);
    line-height: 1.4;
}

.go-iconbtn {
    display: inline-grid;
    place-items: center;
    width: var(--tap);
    height: var(--tap);
    flex: 0 0 auto;
    border: 0;
    border-radius: var(--r-pill);
    background: transparent;
    color: var(--text-2);
    cursor: pointer;
}

.go-iconbtn:active { background: var(--surface-2); }
.go-iconbtn svg { width: 22px; height: 22px; }

.go-main {
    min-width: 0;
    padding: var(--pad) var(--pad) calc(var(--bar-bottom) + var(--safe-bottom) + 5rem);
}

html.standalone .go-main {
    overflow-y: auto;
    overscroll-behavior: contain;
    -webkit-overflow-scrolling: touch;
}

.go-tabbar {
    position: fixed;
    inset-inline: 0;
    bottom: 0;
    z-index: 45;
    display: grid;
    grid-template-columns: repeat(5, 1fr);
    padding-bottom: var(--safe-bottom);
    background: color-mix(in srgb, var(--surface) 92%, transparent);
    backdrop-filter: saturate(1.4) blur(16px);
    border-top: 1px solid var(--line);
}

.go-tab {
    display: grid;
    justify-items: center;
    gap: 2px;
    padding: .5rem 0 .45rem;
    border: 0;
    background: none;
    color: var(--text-3);
    font-size: var(--t-micro);
    font-weight: 600;
    cursor: pointer;
    /* The active marker is a saw cut across the top edge of the tab. */
    border-top: 2px solid transparent;
    margin-top: -1px;
}

.go-tab svg { width: 23px; height: 23px; }
.go-tab[aria-current='page'] { color: var(--text); border-top-color: var(--accent); }
.go-tab[aria-current='page'] svg { color: var(--accent); }

/* --------------------------------------------------------------------------
   4. Cards

   Two families, and the difference is load-bearing. `.panel` describes a
   physical thing -- a cut piece, a sheet, a cabinet -- and is cut square with
   an edge band down its leading side. `.card` holds controls and is rounded.
   -------------------------------------------------------------------------- */

.panel {
    position: relative;
    background: var(--surface);
    border: 1px solid var(--line);
    border-radius: var(--r-panel);
    padding: .875rem 1rem .875rem calc(1rem);
    padding-inline-start: 1.125rem;
    overflow: hidden;
}

.panel::before {
    /* The edge band. */
    content: '';
    position: absolute;
    inset-block: 0;
    inset-inline-start: 0;
    width: 3px;
    background: var(--kerf);
}

.panel--accent::before { background: var(--accent); }
.panel--ok::before { background: var(--ok); }
.panel--warn::before { background: var(--warn); }
.panel--muted::before { background: var(--line-strong); }

.card {
    background: var(--surface);
    border: 1px solid var(--line);
    border-radius: var(--r-control);
    padding: var(--pad);
}

.card--sunken { background: var(--bg-sunken); border-style: dashed; }

.stack { display: grid; gap: var(--gap); }
.stack--tight { gap: .5rem; }
.stack--loose { gap: 1.25rem; }
.row { display: flex; align-items: center; gap: .625rem; }
.row--between { justify-content: space-between; }
.row--wrap { flex-wrap: wrap; }
.grow { flex: 1; min-width: 0; }

/* --------------------------------------------------------------------------
   5. Section headings and the dimension rule

   The dimension rule is the app's structural device: a technical-drawing
   dimension line, tick-terminated, with the label sitting in a gap in the
   middle of it. It appears where the content below is a measured thing, and
   nowhere else -- it means "what follows has dimensions", not "new section".
   -------------------------------------------------------------------------- */

.sec { display: grid; gap: .625rem; }

.sec__head {
    display: flex;
    align-items: baseline;
    justify-content: space-between;
    gap: .5rem;
}

.sec__title { font-size: var(--t-head); font-weight: 700; }
.sec__note { font-size: var(--t-small); color: var(--text-3); }

.dimrule {
    display: flex;
    align-items: center;
    gap: .625rem;
    color: var(--text-3);
    font-size: var(--t-small);
    font-weight: 600;
}

.dimrule::before,
.dimrule::after {
    content: '';
    height: 1px;
    background: var(--line-strong);
    flex: 1;
    /* Tick terminators, drawn as a taller pseudo-edge via border. */
    border-inline: 1px solid var(--line-strong);
    box-sizing: content-box;
    margin-block: 3px;
    border-block: 3px solid transparent;
    background-clip: content-box;
}

.dimrule--start::before { flex: 0 0 12px; }

/* --------------------------------------------------------------------------
   6. Buttons
   -------------------------------------------------------------------------- */

.btn {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    gap: .5rem;
    min-height: var(--tap);
    padding: .5rem 1.125rem;
    border: 1px solid transparent;
    border-radius: var(--r-pill);
    background: var(--surface-2);
    color: var(--text);
    font-size: var(--t-body);
    font-weight: 600;
    cursor: pointer;
    transition: transform .08s ease;
}

.btn:active { transform: scale(.98); }
.btn svg { width: 19px; height: 19px; flex: 0 0 auto; }

.btn--primary { background: var(--accent); color: var(--accent-ink); }
.btn--ghost { background: transparent; border-color: var(--line-strong); color: var(--text-2); }
.btn--quiet { background: transparent; color: var(--text-2); padding-inline: .5rem; }
.btn--danger { background: transparent; border-color: var(--accent); color: var(--accent); }
.btn--block { width: 100%; }
.btn--sm { min-height: 36px; padding: .25rem .75rem; font-size: var(--t-small); }
.btn[disabled] { opacity: .45; pointer-events: none; }

.fab {
    position: fixed;
    inset-inline-end: 1rem;
    bottom: calc(var(--bar-bottom) + var(--safe-bottom) + 1rem);
    z-index: 44;
    display: inline-flex;
    align-items: center;
    gap: .5rem;
    min-height: 52px;
    padding: 0 1.25rem;
    border: 0;
    border-radius: var(--r-pill);
    background: var(--accent);
    color: var(--accent-ink);
    font-weight: 700;
    box-shadow: var(--shadow-lift);
    cursor: pointer;
}

.fab svg { width: 21px; height: 21px; }

/* --------------------------------------------------------------------------
   7. Form controls

   16px minimum on every text field: iOS Safari zooms the viewport on focus for
   anything smaller, and a zoomed viewport in an installed app cannot be zoomed
   back out by the user.
   -------------------------------------------------------------------------- */

.field { display: grid; gap: .3rem; }

.field__label {
    font-size: var(--t-small);
    font-weight: 600;
    color: var(--text-2);
}

.field__hint { font-size: var(--t-micro); color: var(--text-3); }

.input, .select, .textarea {
    width: 100%;
    min-height: var(--tap);
    padding: .5rem .75rem;
    font-size: 16px;
    background: var(--bg-sunken);
    border: 1px solid var(--line);
    border-radius: 10px;
    color: var(--text);
    appearance: none;
}

.input:focus, .select:focus, .textarea:focus {
    border-color: var(--accent);
    outline: none;
    background: var(--surface);
}

.input.num, .input[inputmode='numeric'], .input[inputmode='decimal'] {
    font-variant-numeric: tabular-nums;
    font-weight: 700;
    letter-spacing: 0;
    text-align: start;
    direction: ltr;
}

.select {
    background-image: linear-gradient(45deg, transparent 50%, var(--text-3) 50%),
                      linear-gradient(135deg, var(--text-3) 50%, transparent 50%);
    background-position: calc(0% + 14px) calc(50% + 1px), calc(0% + 19px) calc(50% + 1px);
    background-size: 5px 5px, 5px 5px;
    background-repeat: no-repeat;
    padding-inline-start: 2rem;
}

.textarea { min-height: 88px; line-height: 1.7; resize: vertical; }

/* A measurement field carries its unit inside the box, so the number never has
   to share a line with a label the eye has to re-read.

   `direction: ltr` on the wrapper is load-bearing. The input is LTR (a
   measurement is read left to right) while the wrapper inherited the page's
   RTL, so `padding-inline-end` on the input resolved to the right and
   `inset-inline-end` on the unit resolved to the LEFT -- the unit label sat
   exactly on top of the digits, and the 44 px of padding was reserved on the
   empty side. Both now resolve the same way: number left, unit right. */
.measure { position: relative; direction: ltr; }
.measure .input { padding-inline-end: 2.75rem; }

.measure__unit {
    position: absolute;
    inset-inline-end: .75rem;
    top: 50%;
    transform: translateY(-50%);
    font-size: var(--t-small);
    font-weight: 600;
    color: var(--text-3);
    pointer-events: none;
}

.grid-2 { display: grid; grid-template-columns: minmax(0, 1fr) minmax(0, 1fr); gap: var(--gap); }
.grid-3 { display: grid; grid-template-columns: repeat(3, minmax(0, 1fr)); gap: .5rem; }

/* Segmented control — one choice from a short set, thumb-reachable. */
.seg {
    display: flex;
    padding: 3px;
    gap: 3px;
    background: var(--bg-sunken);
    border: 1px solid var(--line);
    border-radius: var(--r-pill);
}

.seg__opt {
    flex: 1;
    min-height: 38px;
    border: 0;
    border-radius: var(--r-pill);
    background: transparent;
    color: var(--text-2);
    font-size: var(--t-small);
    font-weight: 600;
    cursor: pointer;
    white-space: nowrap;
}

.seg__opt[aria-pressed='true'] { background: var(--surface); color: var(--text); box-shadow: var(--shadow-lift); }

.switch { display: flex; align-items: center; justify-content: space-between; gap: 1rem; min-height: var(--tap); }
.switch input { appearance: none; width: 46px; height: 27px; border-radius: 999px; background: var(--line-strong); position: relative; cursor: pointer; flex: 0 0 auto; transition: background .15s; }
.switch input::after { content: ''; position: absolute; top: 3px; inset-inline-start: 3px; width: 21px; height: 21px; border-radius: 50%; background: #fff; transition: transform .15s; }
.switch input:checked { background: var(--ok); }
.switch input:checked::after { transform: translateX(-19px); }

/* --------------------------------------------------------------------------
   8. Stat readouts

   The decisive number is set large in the brand red; supporting figures stay
   quiet. Only one red number per screen -- if everything is decisive, nothing is.
   -------------------------------------------------------------------------- */

.stat { display: grid; gap: .1rem; }
.stat__value { font-size: var(--t-display); font-weight: 800; line-height: 1.15; min-width: 0; overflow-wrap: anywhere; }
.stat__value--key { color: var(--accent); }

/* A money figure is not a display number. Nine grouped digits at the display
   size measure 326 px once the reader is at 160% text, which is wider than the
   phone; the title size keeps it the biggest thing on the card without being
   the widest thing on the screen. The wrap above is the floor under every other
   long value that finds its way into a stat. */
.stat__value--money { font-size: var(--t-title); }
.stat__unit { font-size: .5em; font-weight: 700; color: var(--text-3); margin-inline-start: .2em; }
.stat__label { font-size: var(--t-small); color: var(--text-2); }

/* min() on the minimum: a fixed rem minimum becomes wider than the viewport
   once the reader enlarges text, and the page starts scrolling sideways. */
.statgrid { display: grid; grid-template-columns: repeat(auto-fit, minmax(min(6.5rem, 100%), 1fr)); gap: .875rem; }

/* And the same trap one level down, which cost three separate bugs before it
   was named. A grid column left at its default `auto` sizes to MAX-content, so
   a single long word, an ungrouped price or a nine-digit total in any one card
   widens its whole column -- and since these stacks run the width of the app,
   that means the whole app. `minmax(0, 1fr)` pins the track to the container
   and lets the content inside it shrink, wrap or ellipsise as it already knows
   how to. Any single-column grid added later belongs on this list. */
.stack, .sec, .field, .stat, .list, .nest, .seed, .invite__reasons {
    grid-template-columns: minmax(0, 1fr);
}

.kv { display: flex; align-items: baseline; justify-content: space-between; gap: .75rem; padding: .4rem 0; border-bottom: 1px dashed var(--line); }
.kv:last-child { border-bottom: 0; }
.kv__k { color: var(--text-2); font-size: var(--t-small); }
.kv__v { font-weight: 700; }

.chip {
    display: inline-flex;
    align-items: center;
    gap: .3rem;
    padding: .15rem .55rem;
    border-radius: var(--r-pill);
    background: var(--surface-2);
    border: 1px solid var(--line);
    font-size: var(--t-micro);
    font-weight: 600;
    color: var(--text-2);
    white-space: nowrap;
}

.chip--ok { color: var(--ok); border-color: color-mix(in srgb, var(--ok) 40%, transparent); background: color-mix(in srgb, var(--ok) 10%, transparent); }
.chip--warn { color: var(--warn); border-color: color-mix(in srgb, var(--warn) 40%, transparent); background: color-mix(in srgb, var(--warn) 10%, transparent); }
.chip--accent { color: var(--accent); border-color: color-mix(in srgb, var(--accent) 40%, transparent); background: color-mix(in srgb, var(--accent) 10%, transparent); }

/* --------------------------------------------------------------------------
   9. Lists
   -------------------------------------------------------------------------- */

.list { display: grid; gap: .5rem; }

.listrow {
    display: flex;
    align-items: center;
    gap: .75rem;
    width: 100%;
    min-height: 56px;
    padding: .625rem .875rem;
    background: var(--surface);
    border: 1px solid var(--line);
    border-radius: var(--r-control);
    text-align: start;
    cursor: pointer;
}

.listrow__body { flex: 1; min-width: 0; }
.listrow__title { font-weight: 600; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.listrow__meta { font-size: var(--t-small); color: var(--text-3); }
.listrow__chev { color: var(--text-3); flex: 0 0 auto; }
.listrow__chev svg { width: 18px; height: 18px; }

.listrow__mark {
    display: grid;
    place-items: center;
    width: 40px; height: 40px;
    flex: 0 0 auto;
    border-radius: 10px;
    background: var(--bg-sunken);
    color: var(--text-2);
}

.listrow__mark svg { width: 21px; height: 21px; }

/* --------------------------------------------------------------------------
   10. Empty states, toasts, sheets
   -------------------------------------------------------------------------- */

.empty {
    display: grid;
    gap: .625rem;
    justify-items: center;
    text-align: center;
    padding: 2.5rem 1rem;
    color: var(--text-2);
}

.empty__art { color: var(--line-strong); }
.empty__art svg { width: 84px; height: 84px; }
.empty__title { font-size: var(--t-head); font-weight: 700; color: var(--text); }
.empty p { max-width: 30ch; margin: 0; font-size: var(--t-small); }

.toaster {
    position: fixed;
    inset-inline: 0;
    bottom: calc(var(--bar-bottom) + var(--safe-bottom) + .75rem);
    z-index: 60;
    display: grid;
    gap: .5rem;
    justify-items: center;
    padding-inline: 1rem;
    pointer-events: none;
}

.toast {
    max-width: min(28rem, 100%);
    padding: .625rem 1rem;
    border-radius: var(--r-pill);
    background: var(--ink);
    color: #f6f1e8;
    font-size: var(--t-small);
    font-weight: 600;
    box-shadow: var(--shadow-lift);
    animation: toast-in .18s ease both;
}

.toast--ok { background: var(--sap); color: #fff; }
.toast--bad { background: var(--aux-dim); color: #fff; }

@keyframes toast-in { from { opacity: 0; transform: translateY(10px); } }

.scrim {
    position: fixed;
    inset: 0;
    z-index: 70;
    background: rgba(12, 9, 6, .55);
    backdrop-filter: blur(2px);
    animation: fade-in .15s ease both;
}

@keyframes fade-in { from { opacity: 0; } }

.sheet {
    position: fixed;
    inset-inline: 0;
    bottom: 0;
    z-index: 71;
    max-height: 88dvh;
    display: flex;
    flex-direction: column;
    background: var(--surface);
    border-radius: 20px 20px 0 0;
    box-shadow: var(--shadow-sheet);
    padding-bottom: var(--safe-bottom);
    animation: sheet-up .22s cubic-bezier(.2, .8, .3, 1) both;
}

@keyframes sheet-up { from { transform: translateY(100%); } }

.sheet__grab { width: 38px; height: 4px; border-radius: 2px; background: var(--line-strong); margin: .625rem auto .25rem; flex: 0 0 auto; }
.sheet__head { display: flex; align-items: center; gap: .5rem; padding: .25rem 1rem .5rem; border-bottom: 1px solid var(--line); flex: 0 0 auto; }
.sheet__title { flex: 1; font-size: var(--t-head); font-weight: 700; }
.sheet__body { overflow-y: auto; overscroll-behavior: contain; padding: 1rem; }
.sheet__foot { padding: .75rem 1rem; border-top: 1px solid var(--line); display: flex; gap: .625rem; flex: 0 0 auto; }
.sheet__foot .btn { flex: 1; }

/* --------------------------------------------------------------------------
   10b. The install invitation

   The one sheet in the app that is not a response to a tap, so it earns its
   entrance: the icon settles in and the three reasons arrive behind it, once,
   quickly. Everything respects prefers-reduced-motion through the global rule.
   -------------------------------------------------------------------------- */

.invite { text-align: center; }

.invite__mark {
    display: inline-grid;
    place-items: center;
    padding: .5rem;
    border-radius: 22px;
    background: var(--bg-sunken);
    animation: invite-mark .42s cubic-bezier(.2, .9, .3, 1.2) both;
}

.invite__mark img { border-radius: 16px; display: block; }

@keyframes invite-mark {
    from { opacity: 0; transform: scale(.7) translateY(10px); }
}

.invite__title {
    font-size: var(--t-title);
    margin: .875rem 0 .375rem;
    animation: invite-rise .34s ease .06s both;
}

.invite__lede {
    color: var(--text-2);
    font-size: var(--t-small);
    max-width: 34ch;
    margin: 0 auto 1.125rem;
    animation: invite-rise .34s ease .12s both;
}

.invite__reasons { display: grid; gap: .75rem; text-align: start; }

.invite__reason {
    display: flex;
    align-items: flex-start;
    gap: .75rem;
    animation: invite-rise .34s ease calc(.18s + var(--i) * .07s) both;
}

.invite__icon {
    display: grid;
    place-items: center;
    width: 36px; height: 36px;
    flex: 0 0 auto;
    border-radius: 10px;
    background: color-mix(in srgb, var(--accent) 12%, transparent);
    color: var(--accent);
}

.invite__icon svg { width: 19px; height: 19px; }

.invite__steps { margin-top: 1.125rem; text-align: start; }
.invite__steps ol { margin: .625rem 0 0; padding-inline-start: 1.25rem; font-size: var(--t-small); }
.invite__steps li { margin-bottom: .5rem; color: var(--text-2); }
.invite__steps li::marker { color: var(--accent); font-weight: 700; }

@keyframes invite-rise {
    from { opacity: 0; transform: translateY(8px); }
}

/* --------------------------------------------------------------------------
   11. The hub

   The opening screen is not a dashboard of statistics. It is the answer to
   "what am I doing right now" -- the project in hand, then the way in.
   -------------------------------------------------------------------------- */

.hero {
    position: relative;
    margin: -1rem -1rem 1.25rem;
    padding: 1.5rem 1rem 1.375rem;
    background: var(--ink);
    color: #f6f1e8;
    overflow: hidden;
}

/* A cutting plan, faint, behind the words: the thing the app makes. */
.hero::after {
    content: '';
    position: absolute;
    inset: 0;
    background-image:
        linear-gradient(to right, rgba(184, 161, 132, .16) 1px, transparent 1px),
        linear-gradient(to bottom, rgba(184, 161, 132, .16) 1px, transparent 1px);
    background-size: 34px 34px;
    mask-image: linear-gradient(to bottom left, #000, transparent 72%);
    pointer-events: none;
}

.hero > * { position: relative; z-index: 1; }
.hero__title { font-size: var(--t-display); font-weight: 800; line-height: 1.25; letter-spacing: -.01em; }
.hero__lede { margin: .5rem 0 1rem; max-width: 34ch; color: #cbbfad; font-size: var(--t-body); }
.hero__actions { display: flex; gap: .625rem; flex-wrap: wrap; }
.hero .btn--ghost { border-color: rgba(246, 241, 232, .3); color: #e6ddd0; }

.tilegrid { display: grid; grid-template-columns: minmax(0, 1fr) minmax(0, 1fr); gap: .625rem; }

.tile {
    display: grid;
    align-content: start;
    gap: .25rem;
    padding: .875rem;
    min-height: 104px;
    background: var(--surface);
    border: 1px solid var(--line);
    border-radius: var(--r-control);
    text-align: start;
    cursor: pointer;
}

.tile__icon { color: var(--accent); }
.tile__icon svg { width: 24px; height: 24px; }
.tile__title { font-weight: 700; font-size: var(--t-body); }
.tile__note { font-size: var(--t-micro); color: var(--text-3); line-height: 1.6; }
.tile--wide { grid-column: 1 / -1; min-height: 0; }

/* --------------------------------------------------------------------------
   12. The cut plan

   The payoff screen. Sheets are drawn at material colour with a real kerf gap
   between parts, so the drawing reads as boards rather than as a bar chart.
   -------------------------------------------------------------------------- */

.nest { display: grid; gap: 1rem; }

.nest__sheet {
    background: var(--bg-sunken);
    border: 1px solid var(--line);
    border-radius: var(--r-panel);
    padding: .5rem;
}

.nest__head {
    display: flex;
    align-items: baseline;
    justify-content: space-between;
    gap: .5rem;
    padding: .125rem .375rem .5rem;
    font-size: var(--t-small);
}

.nest__name { font-weight: 700; }
.nest__waste { color: var(--text-3); }
.nest__canvas { display: block; width: 100%; height: auto; background: var(--panel-fill-2); border-radius: 2px; }

.legend { display: flex; flex-wrap: wrap; gap: .375rem; }

/* --------------------------------------------------------------------------
   13. Tables — cut lists and hardware lists

   A cut list is read across, so it stays a table and scrolls inside itself
   rather than making the page scroll sideways.
   -------------------------------------------------------------------------- */

.tablewrap {
    overflow-x: auto;
    -webkit-overflow-scrolling: touch;
    border: 1px solid var(--line);
    border-radius: var(--r-control);
    background: var(--surface);
}

.table { width: 100%; border-collapse: collapse; font-size: var(--t-small); }
.table th, .table td { padding: .5rem .625rem; text-align: start; white-space: nowrap; }
.table thead th { position: sticky; top: 0; background: var(--surface-2); font-weight: 700; color: var(--text-2); font-size: var(--t-micro); z-index: 1; }
.table tbody tr { border-top: 1px solid var(--line); }
.table td.num, .table th.num { font-variant-numeric: tabular-nums; }
.table tbody tr:nth-child(even) { background: color-mix(in srgb, var(--bg-sunken) 55%, transparent); }

/* --------------------------------------------------------------------------
   14. Server-rendered hub copy

   Present for crawlers and for the first paint before the modules resolve;
   the router removes it once the app takes over.
   -------------------------------------------------------------------------- */

.seed { display: grid; gap: 1.5rem; }
.seed h2 { font-size: var(--t-title); }
.seed h3 { font-size: var(--t-head); }
.seed p { color: var(--text-2); max-width: 62ch; }
.seed details { border-bottom: 1px solid var(--line); padding: .625rem 0; }
.seed summary { font-weight: 600; cursor: pointer; list-style: none; }
.seed summary::-webkit-details-marker { display: none; }
.seed details p { margin: .5rem 0 0; font-size: var(--t-small); }

.go-boot {
    display: grid;
    gap: .75rem;
    justify-items: center;
    padding: 3rem 1rem;
    color: var(--text-3);
    font-size: var(--t-small);
}

.go-boot__bar { width: 120px; height: 3px; border-radius: 2px; background: var(--line); overflow: hidden; }
.go-boot__bar::after { content: ''; display: block; width: 40%; height: 100%; background: var(--accent); animation: boot-slide 1.1s ease-in-out infinite; }

@keyframes boot-slide {
    0% { transform: translateX(120%); }
    100% { transform: translateX(-320%); }
}

/* --------------------------------------------------------------------------
   15. Offline and update notices
   -------------------------------------------------------------------------- */

.netbar {
    display: flex;
    align-items: center;
    justify-content: center;
    gap: .5rem;
    padding: .375rem 1rem;
    background: var(--warn);
    color: #1b1204;
    font-size: var(--t-small);
    font-weight: 600;
}

.netbar--update { background: var(--sap); color: #fff; cursor: pointer; }

/* --------------------------------------------------------------------------
   16. Print — the cut list and the quote leave the phone on paper
   -------------------------------------------------------------------------- */

@media print {
    :root { --bg: #fff; --surface: #fff; --text: #000; --text-2: #333; --line: #bbb; }
    body.go { background: #fff; color: #000; overflow: visible; height: auto; }
    .go-topbar, .go-tabbar, .fab, .toaster, .netbar, .no-print { display: none !important; }
    .go-main { padding: 0; overflow: visible; }
    .go-shell { display: block; height: auto; }
    .panel, .card, .tablewrap { break-inside: avoid; box-shadow: none; }
    .table thead th { background: #eee; }
    .print-only { display: block !important; }
    @page { margin: 12mm; }
}

.print-only { display: none; }

/* --------------------------------------------------------------------------
   17. Motion budget

   One orchestrated moment in this app: the cut plan drawing itself in, part by
   part, the first time a sheet is rendered. Everything else answers a tap.
   -------------------------------------------------------------------------- */

@media (prefers-reduced-motion: reduce) {
    *, *::before, *::after {
        animation-duration: .001ms !important;
        animation-iteration-count: 1 !important;
        transition-duration: .001ms !important;
    }
}

/* --------------------------------------------------------------------------
   18. Wider screens

   The app stays a phone app on a tablet or a desktop; it centres and grows a
   second column rather than stretching one column to 1400px. The tab bar
   becomes a side rail once there is room beside the content.
   -------------------------------------------------------------------------- */

@media (min-width: 700px) {
    .go-main { max-width: 46rem; margin-inline: auto; width: 100%; }
    .go-topbar { padding-inline: 1.25rem; }
    .tilegrid { grid-template-columns: repeat(3, minmax(0, 1fr)); }
    .hero { border-radius: var(--r-control); margin-inline: 0; }
}

@media (min-width: 1000px) {
    .go-shell { grid-template-columns: 15rem minmax(0, 1fr); grid-template-rows: auto 1fr; }
    .go-topbar { grid-column: 1 / -1; }
    .go-main { grid-column: 2; max-width: 54rem; padding-bottom: 4rem; }

    .go-tabbar {
        position: sticky;
        top: var(--bar-top);
        grid-column: 1;
        grid-row: 2;
        align-self: start;
        display: grid;
        grid-template-columns: 1fr;
        height: calc(100dvh - var(--bar-top));
        border-top: 0;
        border-inline-start: 1px solid var(--line);
        background: transparent;
        backdrop-filter: none;
        padding: 1rem .75rem;
        gap: .25rem;
        align-content: start;
    }

    .go-tab {
        grid-auto-flow: column;
        justify-items: start;
        justify-content: start;
        gap: .75rem;
        padding: .625rem .875rem;
        border-top: 0;
        border-radius: var(--r-pill);
        font-size: var(--t-body);
    }

    .go-tab[aria-current='page'] { background: var(--surface); }
    .fab { inset-inline-end: 2rem; bottom: 2rem; }
    .toaster { bottom: 2rem; }
}
