/*
 * Shared styling for the /apps/* pages.
 *
 * A FILE RATHER THAN FOUR INLINE COPIES, which is a deliberate break from the
 * older pages (index, privacy, terms each inline their own <style>). Those are
 * three pages written at three different times and left alone. This is a family
 * that will reach four apps, each with its own support page, and eight copies of
 * the same tokens is eight places for a colour to drift. The older pages are NOT
 * migrated: rewriting working pages nobody asked about is how a small change
 * becomes a risky one.
 *
 * Tokens are lifted verbatim from marketing/privacy/index.html so the app pages
 * and the legal pages are visibly one site.
 */

:root {
	--brand: #7c3aed; --action: #2563eb; --positive: #16a34a;
	--bg: #ffffff; --bg-alt: #f8fafc; --surface: #ffffff;
	--text: #0f172a; --text-2: #475569; --text-3: #94a3b8;
	--border: #e2e8f0; --border-strong: #cbd5e1; --radius: 14px; --radius-sm: 8px; --maxw: 860px;
	--shadow: 0 8px 24px rgba(15, 23, 42, 0.08);
	color-scheme: light;
}

/* Dark tokens live twice on purpose: the media query keeps the page right with JS off,
   and [data-theme] is what the cog (a11y.js) sets, so a forced Light wins over a dark
   system and a forced Dark wins over a light one. Same attribute the home page uses. */
@media (prefers-color-scheme: dark) {
	:root:not([data-theme="light"]) {
		--bg: #0f172a; --bg-alt: #131c30; --surface: #1e293b;
		--text: #f1f5f9; --text-2: #cbd5e1; --text-3: #64748b;
		--border: #334155; --border-strong: #475569; --brand: #8b5cf6; --action: #3b82f6; --positive: #22c55e;
		--shadow: 0 8px 24px rgba(0, 0, 0, 0.4);
		color-scheme: dark;
	}
}
:root[data-theme="dark"] {
	--bg: #0f172a; --bg-alt: #131c30; --surface: #1e293b;
	--text: #f1f5f9; --text-2: #cbd5e1; --text-3: #64748b;
	--border: #334155; --border-strong: #475569; --brand: #8b5cf6; --action: #3b82f6; --positive: #22c55e;
	--shadow: 0 8px 24px rgba(0, 0, 0, 0.4);
	color-scheme: dark;
}

* { box-sizing: border-box; }

body {
	margin: 0;
	font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
	color: var(--text);
	background: var(--bg);
	line-height: 1.65;
	-webkit-font-smoothing: antialiased;
}

a { color: var(--action); text-decoration: none; }
a:hover { text-decoration: underline; }

.container { max-width: var(--maxw); margin: 0 auto; padding: 0 24px; }

/* ALTERNATING BANDS (Julius, 2026-08-24: the app pages read as one undivided
   scroll, and the other app sites band their sections). A `band` is a
   full-bleed strip whose CONTENT still sits in a .container, so the tint runs
   edge to edge while the text keeps the page's measure. `.band-tint` is the
   every-other fill.

   Reusable on every page sharing this stylesheet, and theme-aware by
   construction: --bg-alt already carries its own dark value, so the strip
   follows the site's appearance without a second rule. */
.band { padding: 8px 0 4px; }
.band-tint { background: var(--bg-alt); border-block: 1px solid var(--border); }
/* A callout inside a tinted band would otherwise wear the SAME fill as the
   band and vanish. It steps up to the card surface instead. */
.band-tint .callout { background: var(--surface); }

header { border-bottom: 1px solid var(--border); position: sticky; top: 0; background: var(--bg); z-index: 10; }
.bar { display: flex; align-items: center; justify-content: space-between; padding: 14px 24px; max-width: 1080px; margin: 0 auto; }
.brand { display: flex; align-items: center; gap: 10px; font-weight: 700; font-size: 1.1rem; color: var(--text); }
.brand img { width: 28px; height: 28px; border-radius: 22%; }
.bar nav { display: flex; gap: 18px; align-items: center; }

.btn { display: inline-block; padding: 9px 16px; border-radius: 10px; font-weight: 600; transition: transform 0.2s, box-shadow 0.2s, background 0.2s; }
.btn:hover { text-decoration: none; transform: translateY(-2px); }
.btn-primary { background: var(--action); color: #fff; }
.btn-primary:hover { box-shadow: 0 8px 24px rgba(37, 99, 235, 0.45); }
.btn-ghost { border: 1px solid var(--border); color: var(--text); }
.btn-ghost:hover { box-shadow: var(--shadow); }

/* Hero: the home page's dark gradient, so the apps pages read as the same site
   and not as a doc that borrowed its header. Always dark, whatever the theme, the
   same way the home page hero is. */
.hero {
	background: linear-gradient(150deg, #1e1b4b 0%, #0f172a 52%, #4c1d95 100%);
	color: #fff;
	padding: 64px 0 56px;
}
.hero.tall { text-align: center; padding: 88px 0 76px; }
h1 { font-size: 2.4rem; font-weight: 700; line-height: 1.12; letter-spacing: -0.01em; margin: 0 0 14px; }
.hero.tall h1 { font-size: 3.1rem; }
.lede { color: rgba(255, 255, 255, 0.88); font-size: 1.18rem; line-height: 1.6; margin: 0 0 8px; max-width: 720px; }
.hero.tall .lede { margin: 0 auto 28px; }
.status-badge {
	display: inline-flex; align-items: center; gap: 8px;
	background: rgba(139, 92, 246, 0.22); border: 1px solid rgba(167, 139, 250, 0.6); color: #e9d5ff;
	padding: 7px 18px; border-radius: 999px; font-size: 0.85rem; font-weight: 600; margin-bottom: 24px;
}
/* On an app page the badge sits INSIDE p.hero-store rather than standing alone,
   and there the bottom margin only inflates the line box. */
.hero-store .status-badge { margin-bottom: 0; }
.status-badge .dot { position: relative; width: 8px; height: 8px; border-radius: 50%; background: #4ade80; box-shadow: 0 0 0 3px rgba(74, 222, 128, 0.25); }
.status-badge .dot::after { content: ""; position: absolute; inset: 0; border-radius: 50%; animation: dot-pulse 2.4s ease-out infinite; }
@keyframes dot-pulse { 0% { box-shadow: 0 0 0 0 rgba(74, 222, 128, 0.45); } 70%, 100% { box-shadow: 0 0 0 8px rgba(74, 222, 128, 0); } }
@media (prefers-reduced-motion: reduce) { .status-badge .dot::after { animation: none; } }
.cta-row { display: flex; gap: 14px; flex-wrap: wrap; }
.hero.tall .cta-row { justify-content: center; }
.hero .btn { padding: 15px 34px; font-size: 1.05rem; }
.hero .btn-ghost { background: rgba(255, 255, 255, 0.14); color: #fff; border-color: rgba(255, 255, 255, 0.7); }
.hero .btn-ghost:hover { background: rgba(255, 255, 255, 0.24); box-shadow: 0 8px 24px rgba(2, 6, 23, 0.35); }
.platform-row { display: flex; justify-content: center; flex-wrap: wrap; gap: 12px; margin-top: 32px; font-size: 0.9rem; opacity: 0.85; }
.platform-row span { border: 1px solid rgba(255, 255, 255, 0.35); padding: 5px 16px; border-radius: 16px; }
/* Breadcrumb-ish eyebrow on the app pages, so a reader landing from a search knows where they are. */
.eyebrow { display: inline-block; font-size: 0.8rem; font-weight: 600; letter-spacing: 0.05em; text-transform: uppercase; color: #c4b5fd; margin-bottom: 12px; }
.eyebrow a { color: inherit; }
.updated { color: var(--text-3); font-size: 0.92rem; margin: 0 0 8px; }

main { padding: 16px 0 24px; }
article h2 { font-size: 1.3rem; margin: 36px 0 8px; letter-spacing: -0.01em; }
article h3 { font-size: 1.05rem; margin: 22px 0 6px; }
article p, article li { color: var(--text-2); }
article p { margin: 0 0 14px; }
article ul { margin: 0 0 14px; padding-left: 22px; }
article li { margin: 0 0 7px; }
article strong { color: var(--text); }

.callout { background: var(--bg-alt); border: 1px solid var(--border); border-left: 4px solid var(--brand); border-radius: 12px; padding: 16px 20px; margin: 18px 0 8px; }
.callout p { margin: 0; color: var(--text); }

/* The app roster on /apps/, and the feature grid on an app page. Auto-fit so a
   fourth app and a narrow phone both lay out without a media query each. */
.grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(260px, 1fr)); gap: 16px; margin: 20px 0 8px; }
/* The app roster: two across, so a title and its status pill share a line and four cards make a 2x2. */
.grid.apps { grid-template-columns: repeat(auto-fit, minmax(320px, 1fr)); }

.grid { counter-reset: card; }
.card {
	background: var(--surface); border: 1px solid var(--border); border-radius: var(--radius); padding: 22px;
	counter-increment: card;
	transition: transform 0.25s, box-shadow 0.25s;
}
.card:hover { transform: translateY(-4px); box-shadow: var(--shadow); }
.card h3 { display: flex; align-items: center; flex-wrap: wrap; gap: 8px; margin: 0 0 8px; font-size: 1.1rem; color: var(--text); }
.card p { margin: 0; font-size: 0.95rem; }
/* Glyph left, index right, on one baseline. The numeral is decorative, so it lives in
   the aria-hidden glyph span and never reaches a screen reader. */
.card .glyph { display: flex; align-items: center; justify-content: space-between; font-size: 1.6rem; line-height: 1; margin: 0 0 14px; }
/* A shipped app shows its REAL icon rather than a stand-in emoji (Julius,
   2026-08-24, once Kihon Staff went into review). The hairline keeps a white
   icon from dissolving into a white card, and matters more in dark mode where
   the icon is the brightest thing on the surface. */
.app-icon {
	display: block; width: 40px; height: 40px; border-radius: 22%;
	/* A WHITE icon on a white card needs its edge drawn twice (Julius,
	   2026-08-24). The hairline does the edge, because at 40px a shadow alone
	   is too soft to say where the icon stops. The lift is tinted with the
	   icon's OWN purple rather than black or a heavier grey outline: grey
	   reads as a form control, black on white goes muddy, and the brand tint
	   looks intended. */
	border: 1.5px solid var(--border);
	box-shadow: 0 2px 10px rgba(124, 58, 237, 0.22);
}
/* On the dark hero the same purple would vanish into the purple behind it, so
   that one keeps a deep neutral shadow and needs no border at all. */
.hero-icon { display: block; width: 72px; height: 72px; border-radius: 22%; box-shadow: 0 10px 30px rgba(2, 6, 23, 0.45); flex: none; }

/* Icon and name on ONE line, the way a store lists a product (Julius,
   2026-08-24). Stacked, they read as two separate objects; side by side they
   read as one app. The h1 gives up its own bottom margin to the row so the
   gap above the lede is unchanged. */
.app-lockup { display: flex; align-items: center; gap: 18px; margin: 0 0 14px; }
.app-lockup h1 { margin: 0; }
@media (max-width: 700px) {
	/* A 72px icon beside a 2.4rem headline crowds a phone, so the icon steps
	   down rather than the title wrapping around it. */
	.app-lockup { gap: 14px; }
	.hero-icon { width: 56px; height: 56px; }
}
.card .glyph::after { content: counter(card, decimal-leading-zero); font-variant-numeric: tabular-nums; font-size: 0.8rem; font-weight: 600; letter-spacing: 0.04em; color: var(--text-3); }
/* Cards with no glyph (the feature grid on an app page) still get the numeral. */
.card .n { display: block; font-variant-numeric: tabular-nums; font-size: 0.8rem; font-weight: 600; letter-spacing: 0.04em; color: var(--text-3); margin-bottom: 10px; }
.card .n::before { content: counter(card, decimal-leading-zero); }
:root[data-contrast="high"] .card, :root[data-contrast="high"] .callout, :root[data-contrast="high"] .facts th, :root[data-contrast="high"] .facts td { border-width: 2px; }

/* A card for an app that is not out yet. Muted rather than hidden, because a
   school owner deciding on Kihon wants to know what is coming. */
.card.soon { opacity: 0.72; }
/* The line under a card grid that carries a point the cards do not: on the staff
   app page, the tab switch the heading promises. Quiet, so it reads as a footnote
   to the grid rather than a sixth card. */
.grid-note { margin: 4px 0 8px; font-size: 0.95rem; color: var(--text-2); }
.pill { display: inline-block; font-size: 0.74rem; font-weight: 700; letter-spacing: 0.02em; text-transform: uppercase; padding: 3px 9px; border-radius: 999px; border: 1px solid var(--border); color: var(--text-3); }
.pill.live { color: var(--positive); border-color: currentColor; }

/* Facts table (requirements, privacy summary). A table because it IS one, and a
   reviewer scanning for what the app collects should find rows, not prose. */
.facts { width: 100%; border-collapse: collapse; margin: 8px 0 18px; font-size: 0.95rem; }
.facts th, .facts td { text-align: left; padding: 10px 12px; border-bottom: 1px solid var(--border); vertical-align: top; }
.facts th { color: var(--text); font-weight: 600; width: 34%; }
.facts td { color: var(--text-2); }
.facts tr:last-child th, .facts tr:last-child td { border-bottom: 0; }

/* Screenshot strip (apps/staff). Horizontal scroll rather than wrap: the shots are
   tall, and six of them stacked would push the rest of the page out of reach.
   Each slot carries BOTH appearances and CSS shows the one matching the theme, so
   the cog switches the screenshots along with the site. No script for that part. */
/* One track holding the pills, the segmented-control shape (Julius, 2026-08-24:
   the standalone pills read better contained, like the Name Wheel site), with
   the ACTIVE pill SLIDING between options (transitions.dev tabs-sliding: JS
   writes the active tab's offsetLeft/offsetWidth onto .shot-pill, CSS owns the
   tween). The first paint writes the position with the transition suspended so
   the pill never animates in from width 0. */
.shot-tabs {
	position: relative;
	display: inline-flex; gap: 4px; margin: 0 0 16px; padding: 4px;
	border: 1px solid var(--border); border-radius: 999px; background: var(--surface);
}
.shot-pill {
	position: absolute; top: 4px; left: 0; height: calc(100% - 8px); width: 0;
	background: var(--action); border-radius: 999px;
	transform: translateX(0);
	transition: transform 250ms cubic-bezier(0.22, 1, 0.36, 1), width 250ms cubic-bezier(0.22, 1, 0.36, 1);
	will-change: transform, width;
	z-index: 0; pointer-events: none;
}
.shot-tab {
	position: relative; z-index: 1;
	font: inherit; font-size: 0.9rem; font-weight: 600;
	padding: 8px 20px; border-radius: 999px; cursor: pointer;
	border: none; background: transparent; color: var(--text-2);
	transition: transform 0.15s, color 250ms cubic-bezier(0.22, 1, 0.36, 1);
}
.shot-tab:hover { color: var(--text); }
.shot-tab:active { transform: translateY(1px); }
.shot-tab[aria-pressed="true"] { color: #fff; }
/* The UA ring lands ON the active pill, where blue on blue measures 1.16:1 and a
   keyboard user sees nothing. Offset puts it out on the bar surface instead, and
   --text is the highest-contrast token there in both appearances. */
.shot-tab:focus-visible { outline: 2px solid var(--text); outline-offset: 2px; }

.shots { display: flex; gap: 20px; overflow-x: auto; padding: 4px 0 14px; scroll-snap-type: x mandatory; -webkit-overflow-scrolling: touch; scrollbar-width: none; }
/* Scrollbar hidden for looks only. The strip still scrolls by wheel, drag, arrow key and swipe. */
.shots::-webkit-scrollbar { display: none; }
.shots[hidden] { display: none; }
.shots figure { flex: 0 0 185px; margin: 0; scroll-snap-align: start; text-align: center; }
.shots-ipad figure { flex: 0 0 300px; }
/* The composites already carry their own gradient background, so no border here. The
   width/height attributes hold the slot's height before the file lands. */
.shots img { display: block; width: 100%; height: auto; border-radius: 18px; }
.shots figcaption { margin-top: 8px; font-size: 0.88rem; line-height: 1.45; color: var(--text-2); }

.shots .shot-dark { display: none; }
@media (prefers-color-scheme: dark) {
	:root:not([data-theme="light"]) .shots .shot-light { display: none; }
	:root:not([data-theme="light"]) .shots .shot-dark { display: block; }
}
:root[data-theme="dark"] .shots .shot-light { display: none; }
:root[data-theme="dark"] .shots .shot-dark { display: block; }

/* Only worth saying where the strip actually runs off the edge. */
.scroll-hint { color: var(--text-3); font-size: 0.88rem; margin: 0 0 8px; }
@media (min-width: 900px) { .scroll-hint { display: none; } }
@media (prefers-reduced-motion: reduce) { .shot-tab, .shot-pill { transition: none !important; } }

.cta { text-align: center; padding: 36px 0 56px; display: flex; gap: 12px; justify-content: center; flex-wrap: wrap; }

footer { border-top: 1px solid var(--border); padding: 28px 0; color: var(--text-3); font-size: 0.9rem; text-align: center; }
footer a { color: var(--text-2); margin: 0 8px; }

@media (max-width: 700px) {
	h1 { font-size: 1.7rem; }
	.bar nav a:not(.btn) { display: none; }
	.facts th { width: 42%; }
}

/* Scroll reveal (the home page's). apps.js adds js-reveal to <html> and in-view per element. */
html.js-reveal [data-reveal]:not(.in-view) { opacity: 0; transform: translateY(16px); }
[data-reveal] { transition: opacity 0.6s ease, transform 0.6s ease; }
/* A revealed card is a plain card again: its own quick hover lift, not the slow reveal curve.
   Spelled with two classes so it outranks the [data-reveal] transition above. */
.card[data-reveal] { transition: transform 0.25s, box-shadow 0.25s, opacity 0.6s ease; }
html.js-reveal[data-reduced-motion="true"] [data-reveal] { opacity: 1 !important; transform: none !important; }
@media (prefers-reduced-motion: reduce) { html.js-reveal [data-reveal] { opacity: 1 !important; transform: none !important; } }

/* The closing CTA band. */
.cta { display: flex; gap: 14px; justify-content: center; flex-wrap: wrap; padding: 12px 24px 8px; }

@media (max-width: 640px) {
	h1 { font-size: 2rem; }
	.hero.tall h1 { font-size: 2.3rem; }
	.hero { padding: 48px 0 40px; }
	.hero.tall { padding: 60px 0 52px; }
	.bar nav { gap: 12px; font-size: 0.95rem; }
}

/* ============================================================
   App Store badge (Kihon Staff went live 2026-08-30).

   Apple's artwork is used unmodified and unrecoloured, which is the condition of
   using it at all. The rule is that the badge contrasts with what it SITS ON, so
   the hero (a dark gradient in both themes) always takes the white one, while a
   badge on the themed card surface swaps with the theme.
   ============================================================ */

.store-badge { display: inline-flex; border-radius: 10px; transition: transform 0.2s; }
.store-badge:hover { transform: translateY(-2px); text-decoration: none; }
.store-badge img { display: block; height: 40px; width: auto; }
.store-badge:focus-visible { outline: 2px solid var(--action); outline-offset: 3px; }

/* In the hero, under the lede. */
.hero-store { margin: 22px 0 10px; }
.hero-note { margin: 0; font-size: 0.92rem; color: rgba(255, 255, 255, 0.72); }

/* The hero icon is a link now. Same lift the buttons use. */
.hero-icon-link { display: inline-flex; border-radius: 22%; transition: transform 0.2s; }
.hero-icon-link:hover { transform: translateY(-2px); }

/* On a card the badge follows the theme. Three states, like the shots above:
   light by default, dark under the OS preference unless the reader has forced
   light, and dark when they have forced it. */
/* `flex`, not `inline-flex`: an inline box sits on a text baseline and drags ~8px
   of phantom descender under it, so the Staff card had 30px of slack below the
   badge against 22px of padding everywhere else and read as dropped in. */
.card .store-badge { display: flex; width: max-content; margin-top: 14px; }
/* Same descender, same fix, in the hero. It put 7.4px of slack under the Staff
   badge that the Portal page (whose hero carries a status badge instead) does
   not have, so the two heroes measured differently for no reason a reader could
   see. */
.hero-store .store-badge { display: flex; width: max-content; }
.card .badge-dark { display: none; }
@media (prefers-color-scheme: dark) {
	:root:not([data-theme="light"]) .card .badge-light { display: none; }
	:root:not([data-theme="light"]) .card .badge-dark { display: block; }
}
:root[data-theme="dark"] .card .badge-light { display: none; }
:root[data-theme="dark"] .card .badge-dark { display: block; }

/* The card's icon is a link too, so it must not inherit the link underline. */
.glyph a { display: inline-flex; }
.glyph a:hover { text-decoration: none; }

@media (prefers-reduced-motion: reduce) {
	.store-badge, .hero-icon-link { transition: none; }
	.store-badge:hover, .hero-icon-link:hover { transform: none; }
}

/* The Staff glyph is a 40px icon while its three siblings are 26px emoji, so its
   heading sat 15px lower than the others in the same row. Pre-existing, but the
   badge draws the eye there now. One fixed height lines all four up. */
.apps .glyph { display: flex; align-items: center; min-height: 40px; }
