/* ---------- Storivie theme ----------
   Every token the design system runs on: the two modes, the eight accents,
   the type scale, the logo lockup and the surface texture.

   It lived inside each surface before, which is how the wizard ended up with
   six accents when the app had eight, and with its own slightly wrong plum.
   One file means that cannot happen again: change a value here and every
   surface picks it up.

   Loaded by the prototype, the wizard, the sign-in page and anything built
   next. It has to sit in the same folder as them.

   Not in here: component styles. Cards, modals and rails belong to the
   surface that draws them. This file is only the vocabulary they share. */

/* Fonts are loaded from the page head now, not from here. An @import inside
   a stylesheet serialises: the browser fetches this file, parses it, and only
   then discovers the font request. The <link> in each page head, with a
   preconnect ahead of it, starts both at once. */

/* ---------- TYPE ----------
   Cormorant for display, DM Sans for body and headings, IBM Plex Mono for
   labels and metadata. DM Sans was chosen over Karla, Jost, Manrope and
   Outfit: Karla read heavy at heading sizes.

   The wizard and sign-in page ran on Karla and Jost, which predated that
   decision. The aliases below let their existing rules keep their old names
   while drawing from the same three faces as the app. */
:root{
  /* Same reasoning as --bar-h and --content-max. --card-max was dark-only, so
     the dashboard cards had no cap in light mode either. --page-gutter and
     --heading-weight were declared identically in both blocks, which is one
     value in two places waiting to drift. */
  --heading-weight:  500;
  --page-gutter:     24px;
  --card-max:        620px;
  /* **Dimensions, not colours.** These were declared inside the dark theme
     block, so in light mode --content-max resolved to nothing and every
     container fell back to full width, while --bar-h resolved to nothing and
     collapsed the top bar: the wordmark, the rail search, the collapse
     control and the whole view band disappeared with it.

     A theme must only change colour. Anything a layout depends on lives here
     so it is identical in both. Moved to :root on 13 September.
     21 rules across the app read these two. */
  --bar-h:           71px;
  --content-max:     1100px;
  /* **The lockup is one image again, 19 September.**

     It was four separate rasters stacked as four absolutely positioned layers:
     a portal drawn as artwork, an edge and a mark drawn as alpha masks filled
     with theme colours, and a text layer filled with a gradient that was
     `--text` for its top 55% and `--accent` below. Together they were 112,000
     characters of base64, **77% of this stylesheet**, duplicated again inside
     `sign-in-storivie.html` and `welcome-wizard-storivie.html`.

     Three things were wrong with it and all three are gone.

     **It could not be iterated on.** Four rasters had to register against each
     other pixel for pixel, so any re-export of the artwork broke alignment.

     **The 55% was a hardcoded position**, tied to where the wordmark stopped
     and the rule began in the art. Move either and the gold climbed into the
     letters.

     **Light mode had no doorway.** The portal layer is the one that carries two
     colours at once, so it could not be a mask and had to be artwork, and being
     artwork it carried its own dark ground. On cream that ground rendered as a
     dark rectangle, so the rule at the time was to drop the layer in light mode
     entirely and let the door be carried by the two masks. The purple doorway,
     which is the whole idea of the mark, simply was not there in light mode.

     Now: two PNGs, one per theme, identical artwork apart from the wordmark's
     ink. The doorway survives in both. The accent no longer recolours the
     lockup, which is the deliberate trade and her decision: the brand is fixed,
     the product is the reader's. Gold is what Storivie is; fern is what one
     reader chose this afternoon.

     600 x 160, exactly 15:4, trimmed so any breathing room is CSS rather than
     transparent pixels. */
  --logo-lockup: url("logo-dark-storivie.png");
  --display: var(--font-display);
  --body:    var(--font-sans);
  --util:    var(--font-mono);
}

[data-theme="dark"] {
  /* ---- ACCENT SWATCHES ----
     The picker shows all eight at once, so each needs its own value
     regardless of which accent is active. Same hexes as the accent blocks
     below; these are the only place they appear twice, and they are here so
     a picker on any surface can draw the full set. */
  --sw-gilt:   #C79C4D;  --sw-sienna: #CD9179;
  --sw-peony:  #E693A6;  --sw-slate:  #9EA7B8;
  --sw-orchid: #D194DD;  --sw-fern:   #6CC79B;
  --sw-ink:    #7BA5F0;  --sw-tide:   #5FBFC6;

  --page:            #141119;
  /* Surface character, dark. Plum lifted a step, never a different hue.
     Raised from 0.16 / 0.10 because at those values the layer was doing
     nothing the eye could find. */
  --haze-a:          rgba(64, 46, 82, 0.27);
  --haze-b:          rgba(50, 36, 68, 0.18);
  --grain-url:       url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='140' height='140'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.85' numOctaves='3' stitchTiles='stitch'/%3E%3C/filter%3E%3Crect width='140' height='140' filter='url(%23n)' opacity='1'/%3E%3C/svg%3E");
  --grain-opacity:   0.050;
  --grain-opacity-hi:0.040;
  --surface:         #1F1B26;
  --surface-raised:  #282331;
  --border:          #37313F;
  --border-strong:   #4A4354;
  --text:            #F1EEF3;
  --text-secondary:  #B5AEBD;
  --text-muted:      #8B8492;
  /* Large sans headings at 700 carry enough mass that full --text glares on
     the dark page: 96 Lc where large bold is comfortable in the high 70s.
     Light mode has no such problem, so there it stays --text. */
  --text-heading:    #DCD6E1;

  /* default accent: gilt */
  --accent:          #C79C4D;
  --accent-hover:    #D1AE6D;
  --accent-bg:       rgba(199, 156, 77, 0.09); --accent-fill: linear-gradient(135deg, rgba(232,213,168,0.20) 0%, rgba(199,156,77,0.11) 34%, rgba(140,106,45,0.14) 58%, rgba(239,224,188,0.22) 100%);
  --on-accent:       #141119;
  /* Lighter tone for small or thin accent text. See --accent-text below. */
  --accent-text:     #DFC698;
  /* The arch and book are drawn in the text color, so the mark stays a mark
     rather than becoming a green book on a fern theme. Gilt overrides this
     back to the accent, because gilt is the brand color and the brand mark
     is gold: that is the point of gilt, not an inconsistency. The rule and
     YOUR STORY LIFE always take the accent, whichever one it is. */
  --logo-mark-ink:   var(--text);
  /* The content column's side padding. The header band is full-width now
     and holds its own contents to the column, so nothing has to cancel a
     top padding any more. */
  /* Height of the brand bar: 180px lockup at 15:4 is 48px, plus 11px padding
     either side and a 1px border, so 71. The rail offsets itself by this, so a
     wrong value slides the rail under the bar.

     Was 70px, derived from the old four-layer lockup's 3.86:1. The artwork
     that replaced it on 19 September is 15:4, which is a pixel and a half
     taller at the same width, and the bar was measuring 71 against a token
     that still said 70. */
  /* One cap for every content column, so the nine places that used to
     hard-code it cannot drift apart. */
  /* The dashboard cards. Narrower than the column on purpose, but not so
     narrow that the section rules run a long way past the panel they are
     separating. Grows on a wide screen and stops well short of the column,
     because a goal panel stretched to 1100px is mostly empty. */
  /* A drawn horizontal lockup, not the stacked one cut up. Four layers on
     one canvas: the painted portal, the door's lit edge, the arch and book,
     and the type. Mark and type are separate masks because the mark runs the
     full height while the type stacks into two color bands beside it, so a
     single gradient could not color both. The split is by connected shape
     rather than by column: the "s" tucks in beside the book and a vertical
     cut put it on the wrong side. */
}

/* ---- LIGHT MODE ---- */
[data-theme="light"] {
  /* ---- ACCENT SWATCHES ----
     The picker shows all eight at once, so each needs its own value
     regardless of which accent is active. Same hexes as the accent blocks
     below; these are the only place they appear twice, and they are here so
     a picker on any surface can draw the full set. */
  --sw-gilt:   #B08627;  --sw-sienna: #8E5033;
  --sw-peony:  #A8395A;  --sw-slate:  #4B535E;
  --sw-orchid: #7A2E88;  --sw-fern:   #216D50;
  --sw-ink:    #1E4A96;  --sw-tide:   #196A72;

  --page:            #FAF7F2;
  /* Surface character, light. Far weaker: the page is already warm, and any
     more reads as parchment. */
  --haze-a:          rgba(214, 198, 176, 0.20);
  --haze-b:          rgba(206, 192, 174, 0.12);
  --grain-url:       url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='140' height='140'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.85' numOctaves='3' stitchTiles='stitch'/%3E%3C/filter%3E%3Crect width='140' height='140' filter='url(%23n)' opacity='1'/%3E%3C/svg%3E");
  --grain-opacity:   0.020;
  --grain-opacity-hi:0.015;
  /* Warm, not white. Both of these were #FFFFFF, which put pure white on a
     parchment page across 18 surfaces — the rail, the header band, every
     popover — and that is what made light mode read bright rather than
     creamy. Note that "raised" goes DEEPER here, not lighter: on a light
     page a panel separates by being a shade below the paper, and there is
     nowhere above white to go. */
  --surface:         #FFFDF8;
  --surface-raised:  #F4EFE4;
  --border:          #E4DDD3;
  --border-strong:   #CFC5B8;
  --text:            #1F1A24;
  --text-secondary:  #5C5364;
  --text-muted:      #8A8190;
  --text-heading:    #1F1A24;

  /* default accent: gilt */
  /* **Gilt, lightened two shades. 20 September, her call: "it's so brown and
     almost olive - lighten the main color by like a shade or two."**

     She is right and the number says why. It was #917127, which is hue 42 - a
     true amber, not a green - at 36 percent lightness, and a gold that dark on
     parchment stops reading as metal and starts reading as olive. #B08627 is the
     same hue at 42 percent with a little more saturation, which is the smallest
     move that reads as gold rather than as a darker yellow.

     **The lightening forces the ink on it to flip, and that is an improvement
     rather than a cost.** White on the old gilt was 4.57:1, barely AA and only
     because the gold was so dark. White on the new one is 3.34 and fails. Dark
     ink on the new one is 5.10, better than what was there. So every
     accent-filled control in light mode carries dark text now, which is what
     dark mode has always done.

     `--accent-text` is deliberately NOT lightened. It is the token for small
     accent type, it sits at 4.75:1 on the page, and it is the one place the
     contrast has nowhere to go. The new accent clears 3:1, which is the bar for
     large text and for UI components like the progress bar and the icon rings,
     and that is all it is asked to carry. */
  --accent:          #B08627;
  --accent-hover:    #987226;
  --accent-bg:       rgba(176, 134, 39, 0.07); --accent-fill: linear-gradient(135deg, rgba(176,134,39,0.14) 0%, rgba(214,181,102,0.06) 34%, rgba(134,102,30,0.12) 58%, rgba(226,205,152,0.16) 100%);
  --on-accent:       #1F1A24;
  --accent-text:     #886A25;
  --logo-mark-ink:   var(--text);
  /* Same artwork, dark ink on the wordmark. The gold stays #C79C4D rather than
     dropping to the light theme's accent, because a brand colour that changes
     with the background is not a brand colour. It reads as gold on cream: 2.37:1
     is a bad number for text and a misleading one for a solid mass. The gap
     between the mark's gold and the UI gold narrowed when the light accent was
     lightened to #B08627, which is a happy side effect rather than the reason. */
  --logo-lockup:     url("logo-light-storivie.png");
  /* The content column's padding. Full-width bars cancel these to reach the
     column edges, so they are tokens: hardcoding them meant a breakpoint
     could change one and not the other, and the header would slide under
     the bar above it. */
  /* height of the brand bar */
}


/* ---- TYPE ----
   One job per face. Three faces, two of them from the same superfamily.

     --font-display  Cormorant Garamond. Content only: book, series and
                     universe titles, and the wordmark. In a product about
                     books the serif belongs to the books; if everything is
                     serif nothing is.

                     **Never below 18px.** It is a high-contrast display serif
                     and its hairlines drop out at small sizes, which is why it
                     was reading as weak rather than elegant. Eleven places had
                     it at 15 to 17px.

     --font-sans     Instrument Sans. All structure and emphasis: nav, headers,
                     buttons, filter chips, form labels, empty states, modal
                     headings. Also every number, with tabular figures, so
                     counts and dates line up in a column.

                     Replaced DM Sans on 13 September. DM Sans is the safe
                     choice you reach for before you have decided; Instrument
                     is slightly narrower, has real editorial character in the
                     letterforms, and sits better beside an old-style serif.

     --font-label    Cormorant SC. Small caps, for the labels that used to be
                     set in monospace: SERIES, UNIVERSE, TITLE A-Z, status.

                     Monospace for small data labels is stock interface
                     furniture borrowed from developer tooling. Small caps are
                     a book-typography tradition, from running heads and
                     chapter openers, so they come from this product's own
                     subject rather than from someone else's. Same superfamily
                     as the display face, so the labels and the titles are
                     visibly related.

   IBM Plex Mono is gone. Its numeric jobs went to the sans with tabular
   figures, its label jobs to Cormorant SC.

   Instrument Sans at 500 for headings; --heading-weight stays its own token
   rather than a number repeated in four rules. */
:root{
  --font-display: 'Cormorant Garamond', Georgia, serif;
  --font-sans:    'Instrument Sans', system-ui, -apple-system, sans-serif;
  --font-label:   'Cormorant SC', 'Cormorant Garamond', Georgia, serif;

  /* Kept so nothing that still asks for it breaks, but pointed at the sans.
     Metadata that is numeric wants tabular figures, not a monospace face. */
  --font-mono:    var(--font-sans);
}

/* ---- ACCENTS ----
   One hex per accent won't pass contrast on both modes, so each carries
   two tones: lighter for dark, deeper for light. Same hue either way,
   so it reads as one identity.

   --accent      fills, bars, borders, large type. Carries the identity.
   --accent-text small or thin text, 14px and under.

   The split exists because light strokes on a dark page need noticeably
   more luminance than the same pair needs in reverse, and the display and
   mono faces here are thin. Every accent measured fine in light mode and
   muddy in dark at label sizes, which is a color problem in one mode
   rather than a font problem. */

/* Orchid - brand plum, from the logo. */
/* Sienna — softened terracotta */
[data-theme="dark"][data-accent="sienna"] {
  --accent: #CD9179; --accent-hover: #DAA48F;
  --accent-bg: rgba(205, 145, 121, 0.09); --accent-fill: var(--accent-bg); --on-accent: #141119;
  /* Lighter tone for small or thin accent text: an accent that reads well as
     a fill is too dim for 10px mono on a dark page. */
  --accent-text: #E2BFB1;
}
[data-theme="light"][data-accent="sienna"] {
  --accent: #8E5033; --accent-hover: #74402A;
  --accent-bg: rgba(142, 80, 51, 0.07); --accent-fill: var(--accent-bg); --on-accent: #FFFFFF;
  --accent-text: #854B30;
}
/* Peony — a real pink, for readers who want pink */
[data-theme="dark"][data-accent="peony"] {
  --accent: #E693A6; --accent-hover: #EFA8B7;
  --accent-bg: rgba(230, 147, 166, 0.09); --accent-fill: var(--accent-bg); --on-accent: #141119;
  /* Lighter tone for small or thin accent text: an accent that reads well as
     a fill is too dim for 10px mono on a dark page. */
  --accent-text: #F0C0CB;
}
[data-theme="light"][data-accent="peony"] {
  --accent: #A8395A; --accent-hover: #8D2F4B;
  --accent-bg: rgba(168, 57, 90, 0.07); --accent-fill: var(--accent-bg); --on-accent: #FFFFFF;
  --accent-text: #9E3655;
}
/* Slate — cool grey, a hint of blue retained */
[data-theme="dark"][data-accent="slate"] {
  --accent: #9EA7B8; --accent-hover: #B1B9C7;
  --accent-text: #B4BCC9;
  --accent-bg: rgba(158, 167, 184, 0.09); --accent-fill: var(--accent-bg); --on-accent: #141119;
}
[data-theme="light"][data-accent="slate"] {
  --accent: #4B535E; --accent-hover: #3C434C;
  --accent-text: #4B535E;
  --accent-bg: rgba(75, 83, 94, 0.07); --accent-fill: var(--accent-bg); --on-accent: #FFFFFF;
}
/* Orchid */
[data-theme="dark"][data-accent="orchid"] {
  --accent: #D194DD; --accent-hover: #DDA9E6;
  --accent-bg: rgba(209, 148, 221, 0.09); --accent-fill: var(--accent-bg); --on-accent: #141119;
  /* Lighter tone for small or thin accent text: an accent that reads well as
     a fill is too dim for 10px mono on a dark page. */
  --accent-text: #E4C1EB;
}
[data-theme="light"][data-accent="orchid"] {
  --accent: #7A2E88; --accent-hover: #632270;
  --accent-bg: rgba(122, 46, 136, 0.07); --accent-fill: var(--accent-bg); --on-accent: #FFFFFF;
  --accent-text: #732B80;
}
/* Gilt — bronze. THE DEFAULT, and the brand color, so it is the one accent
   that also paints the logo's arch and book. */
[data-theme="dark"][data-accent="gilt"] {
  --accent: #C79C4D; --accent-hover: #D1AE6D;
  --accent-bg: rgba(199, 156, 77, 0.09);
  /* Gold is a material, and a flat wash cannot read as one: at any alpha a
     desaturated warm mid-tone over plum just goes brown. A gradient gives it
     the thing metal actually has, a light band and a dark band, so the eye
     reads a surface catching light rather than a color someone chose. Only
     gilt needs this; the saturated accents hold their identity flat. */
  --accent-fill: linear-gradient(135deg, rgba(232,213,168,0.20) 0%, rgba(199,156,77,0.11) 34%, rgba(140,106,45,0.14) 58%, rgba(239,224,188,0.22) 100%); --on-accent: #141119;
  /* Gilt alone drifts mustard when it tints a surface: it is the least
     saturated accent and sits at mid lightness, so a plum surface plus gold
     reads as brown rather than as a hint of gold. Half the mix of the other
     accents, and the gold does its work on the rail, the icon and the label
     instead, where it never has to be diluted. */
  --accent-surface: color-mix(in srgb, var(--accent) 5%, var(--surface-raised));
  --accent-surface-strong: color-mix(in srgb, var(--accent) 8%, var(--surface-raised));
  --logo-mark-ink: var(--accent);
  /* Lighter tone for small or thin accent text: an accent that reads well as
     a fill is too dim for 10px mono on a dark page. */
  --accent-text: #DFC698;
}
[data-theme="light"][data-accent="gilt"] {
  /* Same values as the light `:root` default above, and the reasoning is
     written there. These two have to agree: the root block is what a page gets
     before the accent attribute lands, and a mismatch shows as a flash of the
     old gold on load. */
  --accent: #B08627; --accent-hover: #987226;
  --accent-bg: rgba(176, 134, 39, 0.07);
  --accent-fill: linear-gradient(135deg, rgba(176,134,39,0.14) 0%, rgba(214,181,102,0.06) 34%, rgba(134,102,30,0.12) 58%, rgba(226,205,152,0.16) 100%); --on-accent: #1F1A24;
  --accent-surface: color-mix(in srgb, var(--accent) 5%, var(--surface-raised));
  --accent-surface-strong: color-mix(in srgb, var(--accent) 8%, var(--surface-raised));
  --logo-mark-ink: var(--accent);
  --accent-text: #886A25;
}
/* Fern — never the default; green is the currently-reading status */
[data-theme="dark"][data-accent="fern"] {
  --accent: #6CC79B; --accent-hover: #85D4AE;
  --accent-bg: rgba(108, 199, 155, 0.09); --accent-fill: var(--accent-bg); --on-accent: #141119;
  /* Lighter tone for small or thin accent text: an accent that reads well as
     a fill is too dim for 10px mono on a dark page. */
  --accent-text: #AADFC5;
}
[data-theme="light"][data-accent="fern"] {
  --accent: #216D50; --accent-hover: #195841;
  --accent-bg: rgba(33, 109, 80, 0.07); --accent-fill: var(--accent-bg); --on-accent: #FFFFFF;
  --accent-text: #1F664B;
}
/* Ink — true blue, pushed away from Slate */
[data-theme="dark"][data-accent="ink"] {
  --accent: #7BA5F0; --accent-hover: #93B6F5;
  --accent-text: #9DBCF4;
  --accent-bg: rgba(123, 165, 240, 0.09); --accent-fill: var(--accent-bg); --on-accent: #141119;
}
[data-theme="light"][data-accent="ink"] {
  --accent: #1E4A96; --accent-hover: #183B78;
  --accent-text: #1E4A96;
  --accent-bg: rgba(30, 74, 150, 0.07); --accent-fill: var(--accent-bg); --on-accent: #FFFFFF;
}
/* Tide */
[data-theme="dark"][data-accent="tide"] {
  --accent: #5FBFC6; --accent-hover: #7BCDD3;
  --accent-bg: rgba(95, 191, 198, 0.09); --accent-fill: var(--accent-bg); --on-accent: #141119;
  /* Lighter tone for small or thin accent text: an accent that reads well as
     a fill is too dim for 10px mono on a dark page. */
  --accent-text: #A2DADE;
}
[data-theme="light"][data-accent="tide"] {
  --accent: #196A72; --accent-hover: #12545B;
  --accent-bg: rgba(25, 106, 114, 0.07); --accent-fill: var(--accent-bg); --on-accent: #FFFFFF;
  --accent-text: #18646B;
}

/* ---- LEGACY ALIASES ----
   Old variable names now point at the tokens above. Nothing below this
   block needs to know which theme or accent is active. */
:root{
  --bg: var(--page);
  --bg-elevated: var(--surface);
  --card: var(--surface);
  --card-hover: var(--surface-raised);
  --gold: var(--accent);
  --gold-dim: color-mix(in srgb, var(--accent) 32%, transparent);
  --cream: var(--text);
  --cream-dim: var(--text-secondary);
  --muted: var(--text-muted);
  --line: var(--border);
  --accent-deep: color-mix(in srgb, var(--accent) 78%, #000000);
  --on-chip: var(--on-accent);
  --tooltip-bg: #17111C;
  --overlay: rgba(10,7,13,0.78);
  --scrim: rgba(0,0,0,0.30);
  --scrim-soft: rgba(0,0,0,0.16);
  --shadow: rgba(0,0,0,0.45);
  --shadow-md: rgba(0,0,0,0.40);
  --shadow-lg: rgba(0,0,0,0.55);

  /* Status, genre and reaction colors. Tuned for the dark page. */
  --wine: #a3415a;
  --reading-green: #6b7f4a;
  /* The light end of the currently-reading ombre. Its dark end is
     --reading-green itself, which is also the Read colour, so a book being read
     visibly resolves into the colour it will carry once it is finished. */
  --reading-green-lift: #93a86c;
  --ink: #5b70a0;
  --rose: #d17d99;
  --ku-orange: #d1873f;
  --amber: #b8935a;
}

/* Light mode has to darken every one of those or they wash out on paper. */
[data-theme="light"]{
  --card-hover: #F6F1E9;
  --on-chip: #FFFFFF;
  --tooltip-bg: #FFFFFF;
  --overlay: rgba(40,32,45,0.42);
  --scrim: rgba(0,0,0,0.05);
  --scrim-soft: rgba(0,0,0,0.035);
  --shadow: rgba(60,50,70,0.12);
  --shadow-md: rgba(60,50,70,0.10);
  --shadow-lg: rgba(60,50,70,0.18);

  --wine: #8C2A44;
  --reading-green: #4A5C2C;
  --reading-green-lift: #7A9150;
  --wine: #8E2F42;
  --ink: #3B4E80;
  --rose: #A94A67;
  --ku-orange: #A2601E;
  --amber: #8A6528;
}

/* Some hosts (in-app previews, embeds) place the page inside a container
   with its own light background, and html/body color does not always win.
   This is a painted layer pinned to the viewport behind everything, so the
   page is never see-through wherever it is opened. */
.app-backdrop{
  position: fixed;
  inset: 0;
  z-index: -1;
  background: var(--page);
  pointer-events: none;
}
/* ---------- The shared selected surface ----------
   Selected states are mostly the surrounding surface with a little accent
   stirred in, not a panel of accent. Two reasons. A wash of a desaturated
   warm accent over a plum page goes brown at any alpha, which is what made
   gilt read as mustard. And a metallic gradient cannot rescue it: metal
   needs a light band and a dark band, and across a large flat surface that
   just reads as a muddy gradient rather than as gold.

   So the surface stays quiet and the accent moves into the details — the
   icon, the label, the edge — where it shows at full strength and never has
   to be diluted. Derived with color-mix from the live accent and the live
   surface, so it is theme-aware and needs no per-accent definitions. */
:root{
  --accent-surface: color-mix(in srgb, var(--accent) 10%, var(--surface-raised));
  --accent-surface-strong: color-mix(in srgb, var(--accent) 16%, var(--surface-raised));
  --accent-edge: color-mix(in srgb, var(--accent) 55%, transparent);
}
html{ background: var(--page); }
/* follows the theme, so scrollbars, form controls and host chrome agree with
   the page rather than guessing */
:root{ color-scheme: dark; }
[data-theme="light"]{ color-scheme: light; }

/* ============================================================================
   SHAPE, ELEVATION, MOTION, SCALE
   Added 13 September 2026.

   Until now this file was a colour system. Eighty-six colour tokens, and not
   one rule for radius, shadow, timing or tracking, so every page improvised.
   The app alone carried 284 border-radius declarations across ten values:
   3, 4, 5, 6, 7, 8, 9, 10 and 12px plus pills and circles. Nobody can see the
   difference between 3 and 5. Everybody can see that nothing agrees.

   Four radii, not one. A single radius on everything is the SaaS-card look and
   it flattens hierarchy: the point of the scale is that a button and a modal
   are different kinds of object and should not share a corner.
   ========================================================================= */

:root{
  /* ---- RADIUS ----
     Chosen by what the thing IS, not by its size. */
  --r-control: 6px;    /* buttons, inputs, chips, checkboxes, segmented controls */
  --r-card:    10px;   /* book cards, panels, list rows, tiles */
  --r-sheet:   16px;   /* modals, drawers, the welcome wizard, anything overlaid */
  --r-pill:    999px;  /* tags, counts, status pills */
  /* circles stay 50%; they are not on this scale */

  /* ---- MOTION ----
     Two speeds and one curve. Almost all of the modern feel in an interface
     is timing agreeing with itself.

     The curve decelerates hard: things arrive quickly and settle, rather than
     easing in and out symmetrically, which reads slow and soft. */
  --ease:      cubic-bezier(0.2, 0, 0, 1);
  --ease-out:  cubic-bezier(0.33, 1, 0.68, 1);
  --dur-fast:  120ms;  /* colour, opacity, borders: anything that does not move */
  --dur:       200ms;  /* movement, size, anything that travels */
  --dur-sheet: 280ms;  /* drawers and modals, which cover more ground */

  /* ---- TYPE SCALE ----
     The three faces already have jobs, set out under TYPE above, and those do
     not change here. What was missing is a scale, which is why 201 ad-hoc
     letter-spacing declarations accumulated in the app. */
  --t-xs:    0.75rem;     /* 12px, mono metadata */
  --t-sm:    0.8125rem;   /* 13px, secondary and labels */
  --t-base:  0.9375rem;   /* 15px, body */
  --t-md:    1.0625rem;   /* 17px, card titles */
  --t-lg:    1.375rem;    /* 22px, section headings */
  --t-xl:    1.875rem;    /* 30px, page titles */
  --t-2xl:   2.5rem;      /* 40px, hero */

  /* Tracking by role. Serif titles tighten, mono metadata opens up.
     Cormorant Garamond is a high-contrast serif and sets loose by default;
     without the negative tracking, book titles look like they are drifting. */
  --track-display: -0.02em;
  --track-body:     0;
  --track-meta-sm:   0.06em;   /* 12px and up, where the text is big enough to breathe */
  --track-meta:      0.10em;   /* the default for uppercase metadata */
  --track-meta-wide: 0.15em;   /* 9px and under, where letters need pulling apart */

  --weight-body:   400;
  --weight-medium: 500;
  --weight-strong: 600;

  /* Serif needs more leading than sans at the same size. */
  --leading-display: 1.15;
  --leading-body:    1.55;
}

/* ---- ELEVATION ----
   Ready-made shadows rather than loose colours, so a surface asks for a
   height rather than assembling one.

   The two modes get depth from opposite directions, which is the part worth
   keeping. On parchment a shadow works, tinted plum rather than grey, because
   a neutral grey shadow on a warm page reads as a screenshot pasted onto
   paper. On deep plum a shadow is invisible, so depth comes from a hairline
   highlight along the top edge instead, the way light catches a raised edge. */
[data-theme="dark"]{
  --shadow:    rgba(18, 11, 26, 0.55);
  --shadow-md: rgba(18, 11, 26, 0.50);
  --shadow-lg: rgba(10, 6, 16, 0.65);

  --lift-1: 0 1px 2px var(--shadow), inset 0 1px 0 rgba(255,255,255,0.045);
  --lift-2: 0 4px 14px -4px var(--shadow-md), inset 0 1px 0 rgba(255,255,255,0.07);
  --lift-3: 0 18px 44px -12px var(--shadow-lg), inset 0 1px 0 rgba(255,255,255,0.08);

  /* **The seal sits off the card. 20 September, her pick: option D.**

     The reader state chip is the one thing on a card you press, and it did not
     look like it: a 3px shadow on a card that carries its own read as one
     surface. This is the furthest lift the chip can take before it stops
     belonging to the card it is on, and it is written as three parts because
     that is what a raised object actually is - a soft shadow cast on the
     surface, a tight contact shadow directly under it, and a hairline of light
     along the top edge where light would catch it.

     The edge is the part that does the work. A shadow alone reads as a chip on
     a slightly lower card; a lit top edge reads as an object. It is stronger
     here than on the `--lift` scale because those dress panels the size of a
     card, and 8 percent white along the top of a 180px chip is invisible.

     Nothing else on a card is raised, which is the point: the one thing that
     floats is the one thing you press. The ellipsis and the share control stay
     flush on purpose. */
  --seal-edge: rgba(255,255,255,0.28);
  --seal-lift:       0 10px 24px var(--shadow-lg), 0 2px 5px var(--shadow-md),
                     inset 0 1px 0 var(--seal-edge);
  --seal-lift-hover: 0 14px 30px var(--shadow-lg), 0 3px 7px var(--shadow-md),
                     inset 0 1px 0 var(--seal-edge);
  /* A thing that lifts has to land, or the lift is a picture of a control
     rather than a control. */
  --seal-lift-press: 0 1px 3px var(--shadow), inset 0 1px 2px rgba(0,0,0,0.32);

  --grain-opacity-surface: 0.026;
}

[data-theme="light"]{
  --shadow:    rgba(74, 58, 86, 0.10);
  --shadow-md: rgba(74, 58, 86, 0.13);
  --shadow-lg: rgba(62, 48, 74, 0.20);

  --lift-1: 0 1px 2px var(--shadow), inset 0 1px 0 rgba(255,255,255,0.65);
  --lift-2: 0 4px 14px -4px var(--shadow-md), inset 0 1px 0 rgba(255,255,255,0.75);
  --lift-3: 0 18px 44px -12px var(--shadow-lg), inset 0 1px 0 rgba(255,255,255,0.8);

  /* Same geometry, opposite materials. On parchment the shadow is plum-tinted
     and much softer, and the top edge is nearly white, because that is how a
     raised object reads on paper rather than on glass. */
  --seal-edge: rgba(255,255,255,0.72);
  --seal-lift:       0 10px 24px var(--shadow-lg), 0 2px 5px var(--shadow-md),
                     inset 0 1px 0 var(--seal-edge);
  --seal-lift-hover: 0 14px 30px var(--shadow-lg), 0 3px 7px var(--shadow-md),
                     inset 0 1px 0 var(--seal-edge);
  --seal-lift-press: 0 1px 3px var(--shadow), inset 0 1px 2px rgba(74,58,86,0.20);

  --grain-opacity-surface: 0.011;
}

/* ---- GRAIN ON RAISED SURFACES ----
   The paper grain is the most distinctive thing in this system and it only
   ever sat on the backdrop, which left cards and panels reading as flat
   rectangles on a textured page. At roughly half the backdrop opacity it
   gives a raised surface the same material as everything under it.

   Needs position on the host. Skipped entirely when a surface is scrolling
   content, because a fixed texture under moving text shimmers. */
.grained{ position: relative; isolation: isolate; }
.grained::before{
  content: "";
  position: absolute;
  inset: 0;
  pointer-events: none;
  border-radius: inherit;
  background-image: var(--grain-url);
  opacity: var(--grain-opacity-surface);
  /* Behind the content via a negative index inside the isolate above, rather
     than by giving every child position:relative.

     The companion rule this replaces was `.grained > *{ position: relative }`,
     which quietly turns every direct child into a containing block. Anything
     absolutely positioned deeper inside then anchors to the child instead of
     to the grained element. That exact rule, copied onto .card in the app,
     detached the per-book ellipsis from the share button it is meant to sit
     beside. Fixed there and here on 13 September; the utility was not in use
     yet, so it never showed, but it was the same landmine. */
  z-index: -1;
}


/* ---- MOTION, RESPECTED ----
   Everything above runs through these two durations, so switching them off is
   one rule rather than an audit. */
@media (prefers-reduced-motion: reduce){
  :root{
    --dur-fast:  0.01ms;
    --dur:       0.01ms;
    --dur-sheet: 0.01ms;
  }
  *, *::before, *::after{
    animation-duration: 0.01ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: 0.01ms !important;
    scroll-behavior: auto !important;
  }
}

/* ---- ACCENT GRAVEYARD: CLEARED ----
   --moss, --teal, --plum and --clay were defined in both modes and referenced
   nowhere. Removed 13 September. They are older generations of the accent
   system, left behind when it was redone.

   The reasoning is already written down properly in
   `color-reference-storivie.md`, which owns it: Clay was cut for reading too
   pink, Copper for being too orange-dominant, both replaced by Sienna; Heather
   was cut as too muted; olive and moss were rejected for sitting too close to
   Fern and to the currently-reading green. Not repeated here, because two
   copies of a decision is how they come to disagree.

   Worth noting that the hexes here had already drifted from the ones that doc
   records as cut, so these were an even older generation than the documented
   one.

*/

/* ---- STATUS COLOUR: UNIFIED, DELIBERATELY ----
   --ondeck-green, --waiting-green, --read-blue and --choose-green were defined
   in both modes and referenced nowhere. Removed 13 September.

   Status colour was unified: one green, --reading-green, which is alive in 20
   places and stays. The others are what unification left behind.

   WHY THIS KEEPS HAPPENING, AND THE RULE THAT STOPS IT

   This file predates Storivie. It grew out of Samantha's Shelf, a personal
   reading tracker, and a lot of what looks like an unfinished Storivie feature
   is actually a finished decision from a different product.

   So: **a token defined here and referenced nowhere is a decision that was
   made, not a feature that was missed.** Check before building on one. Twice
   on 13 September a design idea was proposed on top of dead tokens, genre
   colour and then status colour, and both had already been tried and dropped
   for good reasons. The tokens were the evidence and got read backwards.

   If a token is dead, delete it and write down why, the way these two blocks
   do. A gap with a note is safe. A gap without one is an invitation.

*/

/* ---- GENRE AND AUTHOR COLOUR: REMOVED, DELIBERATELY ----
   Seven --genre-* tokens lived here in both modes and were referenced nowhere.
   GENRE_COLOR in the app is kept deliberately empty so genreColor() falls back
   to gold, and authorColor() ignores its argument and returns the secondary
   text colour. Both are stubs holding their call sites open, not unfinished
   work.

   Cleared out 13 September along with a genre-edge utility added earlier the
   same day, before anyone noticed the tokens were dead and assumed they were
   meant to be used.

   Do not bring it back. Two reasons, and the second is the real one.

   The catalog carries 24 genres and the set grows with every author sweep, two
   more on 13 September alone. Seven colours never covered it, and a palette
   stretched across 24 either runs out or collapses into shades nobody can tell
   apart. The moment a reader cannot distinguish Fantasy Romance from Fantasy
   Dark Romance at a glance, the colour is decoration wearing the costume of
   information. The genre chip already says the word.

   More importantly, the accent system is the identity: eight accents the
   reader picks, with gold holding everything together. Genre colour competes
   with the one thing the reader chose. A grid where every card carries a
   different coloured edge stops reading as a palette and starts reading as a
   filing cabinet.

*/

/* ---- TYPE UTILITIES ----
   Added 13 September with the new stack. */

/* Small caps labels. Real small caps from Cormorant SC, not the browser's
   synthesised version, which just shrinks capitals and looks it. */
.sc{
  font-family: var(--font-label);
  font-weight: 600;
  letter-spacing: var(--track-meta);
  text-transform: lowercase;   /* the face supplies the capitals */
  font-feature-settings: "smcp";
}

/* Every number in the interface. Tabular figures so a column of page counts
   or dates aligns instead of shuffling by a pixel per digit. */
.num, .count, [data-num]{
  font-variant-numeric: tabular-nums;
  font-feature-settings: "tnum";
}

/* Book titles wrap badly because they are long and often end on a short word.
   Balance evens the lines out and stops the single-word last line. */
.title-display{
  font-family: var(--font-display);
  font-weight: 500;
  letter-spacing: var(--track-display);
  line-height: var(--leading-display);
  text-wrap: balance;
}

/* Series names in italic against roman titles: a second level of hierarchy
   out of a face already being paid for. Italic 500 is already in the load. */
.series-name{
  font-family: var(--font-display);
  font-style: italic;
  font-weight: 500;
  letter-spacing: var(--track-display);
}

h1, h2, h3, h4, .h-balance{ text-wrap: balance; }
p, .prose{ text-wrap: pretty; }

/* ============================================================
   CHECKBOXES AND RADIOS
   ============================================================

   **Every checkbox in the product was the browser's own. 20 September, and
   she caught it on the search rail: a white square with a grey border sitting
   on a dark panel.**

   Three places set `accent-color: var(--accent)` and stopped there, which
   looked like styling and is not. `accent-color` only tints the *checked*
   state. Unchecked, the control is whatever the operating system draws, which
   on a dark surface is a bright white box, and in light mode is a box with a
   border that matches nothing else on the page. Another four checkboxes set
   nothing at all.

   So this is one definition, here rather than in any one page, because the
   app, the admin console, the welcome wizard and the sign-in page all load
   this file and a control that looks different in the wizard than on the shelf
   is the same drift as two spellings of one field name.

   Sized in em so a checkbox next to 12px help text is smaller than one next to
   a 15px label, without anybody having to remember to set it. */
input[type="checkbox"],
input[type="radio"]{
  appearance: none;
  -webkit-appearance: none;
  flex: none;
  width: 1.05em;
  height: 1.05em;
  margin: 0;
  display: inline-grid;
  place-content: center;
  vertical-align: -0.16em;
  cursor: pointer;
  background: color-mix(in srgb, var(--text-muted) 10%, transparent);
  border: 1.5px solid var(--border-strong);
  border-radius: 4px;
  transition: background var(--dur, .18s) var(--ease, ease),
              border-color var(--dur, .18s) var(--ease, ease);
}
input[type="radio"]{ border-radius: 50%; }

input[type="checkbox"]:hover,
input[type="radio"]:hover{ border-color: var(--accent); }

/* The tick is drawn rather than typed. A glyph would come from whatever font
   happens to be resolved, at whatever weight, and the one place a checkmark
   already appears in the product it is an inline SVG for exactly that reason. */
input[type="checkbox"]::before{
  content: "";
  width: 0.62em;
  height: 0.62em;
  transform: scale(0);
  transition: transform var(--dur, .18s) var(--ease, ease);
  background-color: var(--on-accent, #150f1a);
  clip-path: polygon(14% 45%, 0 60%, 39% 100%, 100% 22%, 85% 8%, 38% 71%);
}
input[type="radio"]::before{
  content: "";
  width: 0.5em;
  height: 0.5em;
  border-radius: 50%;
  transform: scale(0);
  transition: transform var(--dur, .18s) var(--ease, ease);
  background-color: var(--on-accent, #150f1a);
}
input[type="checkbox"]:checked,
input[type="radio"]:checked{
  background: var(--accent);
  border-color: var(--accent);
}
input[type="checkbox"]:checked::before,
input[type="radio"]:checked::before{ transform: scale(1); }

/* A mixed state is a real answer and nothing in the product drew one: the
   select-all box on a list where some rows are picked showed as unchecked,
   which says none are. */
input[type="checkbox"]:indeterminate{
  background: var(--accent);
  border-color: var(--accent);
}
input[type="checkbox"]:indeterminate::before{
  transform: scale(1);
  clip-path: none;
  height: 2px;
  width: 0.6em;
  border-radius: 1px;
}

/* Keyboard focus has to be visible on both fills and in both themes. */
input[type="checkbox"]:focus-visible,
input[type="radio"]:focus-visible{
  outline: 2px solid var(--accent);
  outline-offset: 2px;
}
input[type="checkbox"]:disabled,
input[type="radio"]:disabled{
  cursor: not-allowed;
  opacity: .45;
}
@media (prefers-reduced-motion: reduce){
  input[type="checkbox"], input[type="radio"],
  input[type="checkbox"]::before, input[type="radio"]::before{ transition: none; }
}
