PageSourceSearch

https://lomia.ai/

html lomia.ai collected 2026-10-05 14:00:02 UTC 82,592 bytes, 1,059 lines download raw bytes

1<!DOCTYPE html>
2<html lang="en">
3<head>
4<meta charset="UTF-8" />
5<meta name="viewport" content="width=device-width, initial-scale=1, viewport-fit=cover" />
6<title data-i18n="meta.title">Lomia — Learn a language by living inside stories</title>
7<meta name="description" data-i18n-meta="meta.desc" content="Lomia turns anything you read into spoken story episodes — then listens as you say each line back, and brings the words you stumble on right back to you." />
8<link rel="icon" type="image/svg+xml" href="/favicon.svg" />
9
10<link rel="preconnect" href="https://fonts.googleapis.com" />
11<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
12<link rel="preconnect" href="https://cdn.jsdelivr.net" crossorigin />
13<link href="https://fonts.googleapis.com/css2?family=Fraunces:ital,opsz,wght@0,9..144,400;0,9..144,500;0,9..144,600;0,9..144,700;1,9..144,500;1,9..144,600&family=Manrope:wght@300;400;500;600;700;800&family=Noto+Serif+SC:wght@400;600;700&family=Noto+Sans+SC:wght@300;400;500;700&family=JetBrains+Mono:wght@400;500&display=swap" rel="stylesheet" />
14<!-- 霞鹜文楷 (LXGW WenKai Screen) — warm kaiti for Chinese, per Sage1.0 -->
15<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/lxgw-wenkai-screen-web/style.css" />
16
17<style>
18/* ============================================================
19   FABLE · bilingual landing — warm editorial Apple
20   ============================================================ */
21:root{
22  --paper:#f6f3ec; --paper-pure:#fffdf9;
23  --night:#0d0b09; --night-2:#16120e;
24  --ink:#15110c; --ink-soft:#3a342b; --mute:#6f6657; --faint:#a59a87;
25  --cream:#efe7d8; --cream-mute:#a79c89;
26  --ember:#df5a36; --ember-bright:#f06a44; --ember-soft:#f1a98e; --ember-glow:rgba(223,90,54,.16);
27  --line:rgba(21,17,12,.10); --line-strong:rgba(21,17,12,.18); --line-cream:rgba(239,231,216,.14);
28  --r-sm:14px; --r:22px; --r-lg:32px; --r-xl:42px;
29  --shadow-soft:0 2px 6px rgba(21,17,12,.04), 0 18px 50px -20px rgba(21,17,12,.18);
30  --shadow-lift:0 4px 12px rgba(21,17,12,.06), 0 40px 90px -30px rgba(21,17,12,.32);
31  --shadow-ember:0 18px 50px -16px rgba(223,90,54,.5);
32  --max:1180px; --ease:cubic-bezier(.22,1,.36,1); --ease-soft:cubic-bezier(.4,0,.2,1);
33  --font-display:"Fraunces", "Cormorant Garamond", Georgia, serif;
34  --font-sans:"Manrope", -apple-system, BlinkMacSystemFont, sans-serif;
35  --font-mono:"JetBrains Mono", ui-monospace, "SF Mono", monospace;
36  --tracking-display:-.02em; --tracking-h2:-.018em; --display-lh:1.05; --body-lh:1.6;
37  --display-wght:600;
38}
39/* ---- Chinese typographic overrides (霞鹜文楷, per Sage1.0) ---- */
40html[lang="zh"]{
41  --font-display:"LXGW WenKai Screen", "Noto Serif SC", "STKaiti", "Kaiti SC", serif;
42  --font-sans:"LXGW WenKai Screen", "Noto Sans SC", -apple-system, "PingFang SC", sans-serif;
43  --tracking-display:0em; --tracking-h2:0em; --display-lh:1.12; --body-lh:1.85;
44  --display-wght:700;
45}
46html[lang="zh"] .serif,html[lang="zh"] .serif-i,html[lang="zh"] em.acc{font-style:normal}
47html[lang="zh"] .lead{letter-spacing:0}
48html[lang="zh"] body{letter-spacing:0}
49html[lang="zh"] .display{font-size:clamp(2.3rem,5.6vw,5.4rem)}
50html[lang="zh"] .h2{font-size:clamp(1.85rem,4vw,3.5rem)}
51html[lang="zh"] .eyebrow,html[lang="zh"] .marquee-cap,html[lang="zh"] .foot-col h5,html[lang="zh"] .tag,html[lang="zh"] .cl{letter-spacing:.14em}
52
53*,*::before,*::after{box-sizing:border-box;margin:0;padding:0}
54html{scroll-behavior:smooth;-webkit-text-size-adjust:100%}
55@media (prefers-reduced-motion:reduce){html{scroll-behavior:auto}}
56body{font-family:var(--font-sans);background:var(--paper);color:var(--ink);line-height:var(--body-lh);font-weight:400;letter-spacing:-.01em;-webkit-font-smoothing:antialiased;text-rendering:optimizeLegibility;overflow-x:hidden}
57.grain{position:fixed;inset:0;z-index:9999;pointer-events:none;opacity:.04;mix-blend-mode:multiply;background-image:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='220' height='220'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='.9' numOctaves='3'/%3E%3C/filter%3E%3Crect width='100%25' height='100%25' filter='url(%23n)'/%3E%3C/svg%3E")}
58::selection{background:var(--ember);color:#fff}
59a{color:inherit;text-decoration:none}
60img{max-width:100%;display:block}
61.wrap{max-width:var(--max);margin-inline:auto;
61padding-inline:clamp(20px,5vw,40px)}
62
63/* Typography */
64.eyebrow{font-family:var(--font-mono);font-size:.7rem;font-weight:500;letter-spacing:.32em;text-transform:uppercase;color:var(--ember);display:inline-flex;align-items:center;gap:.7em}
65.eyebrow::before{content:"";width:26px;height:1px;background:var(--ember);opacity:.6}
66.serif{font-family:var(--font-display);font-weight:400}
67.serif-i{font-family:var(--font-display);font-style:italic;font-weight:400}
68em.acc{font-family:var(--font-display);font-style:italic;font-weight:400;color:var(--ember)}
69h1,h2,h3{font-weight:var(--display-wght);letter-spacing:var(--tracking-h2);line-height:1.04}
70.display,.h2,.cta .big,.feat-copy h3{font-family:var(--font-display)}
71.display{font-size:clamp(2.5rem,6vw,5.5rem);line-height:var(--display-lh);letter-spacing:var(--tracking-display);font-weight:var(--display-wght)}
72.h2{font-size:clamp(2rem,4.3vw,3.8rem);letter-spacing:var(--tracking-h2);line-height:1.08}
73.lead{font-size:clamp(1.08rem,1.5vw,1.42rem);line-height:1.6;color:var(--mute);font-weight:400;max-width:42ch;letter-spacing:-.012em}
74
75/* Buttons */
76.btn{display:inline-flex;align-items:center;gap:.55em;font-family:var(--font-sans);font-size:.98rem;font-weight:600;letter-spacing:-.01em;padding:.92em 1.6em;border-radius:999px;cursor:pointer;border:1px solid transparent;transition:transform .4s var(--ease),box-shadow .4s var(--ease),background .3s var(--ease-soft),color .3s var(--ease-soft);white-space:nowrap}
77.btn-primary{background:var(--ember);color:#fff;box-shadow:var(--shadow-ember)}
78.btn-primary:hover{background:var(--ember-bright);transform:translateY(-2px);box-shadow:0 24px 60px -16px rgba(223,90,54,.62)}
79.btn-ghost{background:transparent;color:var(--ink);border-color:var(--line-strong)}
80.btn-ghost:hover{border-color:var(--ink);transform:translateY(-2px)}
81.btn-ghost .arr{transition:transform .4s var(--ease)}
82.btn-ghost:hover .arr{transform:translateY(3px)}
83.on-dark .btn-ghost{color:var(--cream);border-color:var(--line-cream)}
84.on-dark .btn-ghost:hover{border-color:var(--cream)}
85
86/* Lang toggle */
87.lang-toggle{display:inline-flex;border:1px solid var(--line-strong);border-radius:999px;padding:3px;gap:2px;flex:none}
88.lang-toggle button{font-family:var(--font-mono);font-size:.74rem;font-weight:500;border:none;background:none;color:var(--mute);padding:.34em .72em;border-radius:999px;cursor:pointer;transition:background .25s,color .25s;letter-spacing:.04em}
89.lang-toggle button.active{background:var(--ink);color:var(--paper)}
90
91/* Nav */
92.nav{position:fixed;top:0;left:0;right:0;z-index:100;transition:background .4s var(--ease-soft),backdrop-filter .4s,border-color .4s,padding .4s var(--ease);border-bottom:1px solid transparent}
93.nav-inner{max-width:var(--max);margin-inline:auto;padding:20px clamp(20px,5vw,40px);display:flex;align-items:center;justify-content:space-between;gap:24px}
94.nav.scrolled{background:rgba(246,243,236,.72);backdrop-filter:saturate(180%) blur(20px);-webkit-backdrop-filter:saturate(180%) blur(20px);border-color:var(--line)}
95.nav.scrolled .nav-inner{padding-block:13px}
96.brand{display:flex;align-items:center;gap:.6em;font-size:1.5rem}
97.brand .mark{width:30px;height:30px;flex:none}
98.brand b{font-family:"Fraunces","Cormorant Garamond",serif;font-weight:600;font-size:1.72rem;letter-spacing:.005em}
99.nav-links{display:flex;align-items:center;gap:32px}
100.nav-links a.lnk{font-size:.92rem;font-weight:500;color:var(--ink-soft);transition:color .25s}
101.nav-links a.lnk:hover{color:var(--ember)}
102.nav-cta{display:flex;align-items:center;gap:12px}
103@media (max-width:880px){.nav-links .lnk{display:none}}
104
105/* Hero */
106.hero{position:relative;padding:clamp(140px,18vh,210px) 0 clamp(60px,8vh,110px);overflow:hidden}
107.hero-bg{position:absolute;inset:0;z-index:0;pointer-events:none}
108.hero-bg::before{content:"";position:absolute;inset:-10%;background:radial-gradient(60% 50% at 78% 8%,var(--ember-glow),transparent 60%),radial-gradient(50% 45% at 12% 30%,rgba(223,142,54,.10),transparent 60%)}
109.hero-grid{position:relative;z-index:1;display:grid;grid-template-columns:1.06fr .94fr;gap:clamp(30px,5vw,72px);align-items:center}
110.hero-copy{max-width:660px}
111.hero h1{margin:.32em 0 .5em}
112.hero h1 .l1{display:block;color:var(--ink)}
113.hero h1 .l2{display:block;color:var(--ember)}
114.hero .lead{max-width:42ch;margin-bottom:2.1em}
115.hero-cta{display:flex;align-items:center;gap:16px;flex-wrap:wrap}
116.hero-note{margin-top:1.5em;font-size:.84rem;color:var(--faint);display:flex;align-items:center;gap:.6em}
117.hero-note .dot{width:6px;height:6px;border-radius:50%;background:#3aa06a;box-shadow:0 0 0 4px rgba(58,160,106,.16);flex:none}
118.rise{opacity:0;transform:translateY(22px)}
119.js .hero .rise{animation:rise 1s var(--ease) forwards}
120.js .hero .rise.d1{animation-delay:.05s}.js .hero .rise.d2{animation-delay:.16s}.js .hero .rise.d3{animation-delay:.27s}.js .hero .rise.d4{animation-delay:.38s}.js .hero .rise.d5{animation-delay:.5s}
121@keyframes rise{to{opacity:1;transform:none}}
122
123/* Device mockup */
124.device{position:relative;z-index:1;justify-self:center;width:min(360px,86vw);background:linear-gradient(180deg,#fffefb,#f3eee3);border:1px solid var(--line);border-radius:var(--r-xl);padding:18px;box-shadow:var(--shadow-lift);will-change:transform}
125.js .device{animation:float 7s ease-in-out infinite}
126@keyframes float{0%,100%{transform:translateY(0)}50%{transform:translateY(-14px)}}
127.dev-top{display:flex;align-items:center;justify-content:space-between;padding:4px 8px 16px}
128.dev-top .scene{font-family:var(--font-mono);font-size:.66rem;letter-spacing:.12em;text-transform:uppercase;color:var(--faint)}
129.score-ring{--v:92;width:46px;height:46px;border-radius:50%;display:grid;place-items:center;background:conic-gradient(var(--ember) calc(var(--v)*1%),rgba(21,17,12,.08) 0);position:relative}
130.score-ring::before{content:"";position:absolute;inset:5px;border-radius:50%;background:#fffefb}
131.score-ring b{position:relative;font-family:var(--font-mono);font-size:.92rem;font-weight:500;color:var(--ink)}
132.bubble{background:#fff;border:1px solid var(--line);border-radius:18px 18px 18px 6px;padding:12px 15px;font-size:.86rem;color:var(--mute);margin-bottom:12px;max-width:84%;font-family:"Manrope",sans-serif}
133.line-card{background:#fff;border:1px solid var(--line-strong);border-radius:18px;padding:16px 16px 14px;box-shadow:0 10px 30px -18px rgba(21,17,12,.4)}
134.line-card .who{font-family:var(--font-mono);font-size:.6rem;letter-spacing:.18em;text-transform:uppercase;color:var(--ember);margin-bottom:9px}
135.line-card .txt{font-family:"Manrope",sans-serif;font-size:1.06rem;line-height:1.45;letter-spacing:-.02em;color:var(--ink);font-weight:500}
136.line-card .txt .w-now{color:var(--ember)}
137.line-card .txt .w-miss{color:var(--faint);text-decoration:underline wavy var(--ember-soft) 1.5px;text-underline-offset:4px}
138.wave{display:flex;align-items:center;gap:3px;height:34px;margin:15px 2px 13px}
139.wave i{flex:1;background:linear-gradient(180deg,var(--ember-soft),var(--ember));border-radius:3px;height:30%;opacity:.85}
140.js .wave i{animation:wv 1.4s var(--ease-soft) infinite}
141@keyframes wv{0%,100%{height:22%}50%{height:90%}}
142.metrics{display:grid;grid-template-columns:1fr 1fr;gap:9px 16px;margin:4px 2px 14px}
143.metric .ml{display:flex;justify-content:space-between;font-size:.66rem;color:var(--mute);margin-bottom:5px}
144.metric .ml b{font-family:var(--font-mono);font-weight:500;color:var(--ink)}
145.metric .bar{height:4px;border-radius:3px;background:rgba(21,17,12,.08);overflow:hidden}
146.metric .bar span{display:block;height:100%;border-radius:3px;background:var(--ember)}
147.metric.jade .bar span{background:#3aa06a}
148.dev-actions{display:flex;align-items:center;justify-content:center;gap:14px;padding:6px 0 4px}
149.rec{width:58px;height:58px;border-radius:50%;background:var(--ember);border:none;cursor:pointer;display:grid;place-items:center;box-shadow:0 12px 30px -8px rgba(223,90,54,.6);position:relative}
150.rec::after{content:"";position:absolute;inset:-7px;border-radius:50%;border:2px solid var(--ember);opacity:.4}
151.js .rec::after{animation:pulse 2.2s ease-out infinite}
152@keyframes pulse{0%{transform:scale(.9);opacity:.5}100%{transform:scale(1.5);opacity:0}}
153.rec span{width:18px;height:18px;border-radius:5px;background:#fff}
154.ghost-btn{width:46px;height:46px;border-radius:50%;border:1px solid var(--line-strong);background:#fff;display:grid;place-items:center;color:var(--ink-soft)}
155
156/* Marquee */
157.marquee{border-block:1px solid var(--line);padding:34px 0;overflow:hidden;background:var(--paper-pure)}
158.marquee .row{display:flex;gap:60px;width:max-content;align-items:center}
159.js .marquee .row{animation:slide 40s linear infinite}
160.marquee:hover .row{animation-play-state:paused}
161@keyframes slide{to{transform:translateX(-50%)}}
162.marquee .lbl{font-family:var(--font-display);font-size:clamp(1.4rem,3vw,2.3rem);color:var(--faint);white-space:nowrap}
163.marquee .lbl em{font-style:italic;color:var(--ink-soft)}
164html[lang="zh"] .marquee .lbl em{font-style:normal;color:var(--ember)}
165.marquee .sep{width:7px;height:7px;border-radius:50%;background:var(--ember-soft);flex:none}
166.marquee-cap{text-align:center;font-family:var(--font-mono);font-size:.7rem;letter-spacing:.28em;text-transform:uppercase;color:var(--faint);padding-bottom:26px}
167
168/* Sections */
169section{position:relative}
170.sec{padding:clamp(80px,12vh,150px) 0}
171.sec-head{max-width:820px;margin-bottom:clamp(48px,7vh,86px)}
172.sec-head .h2{margin:.35em 0 .4em}
173.sec-head p{font-size:clamp(1.05rem,1.4vw,1.3rem);color:var(--mute);max-width:56ch;line-height:1.55}
174.dark{background:radial-gradient(120% 80% at 50% -10%,var(--night-2),var(--night));color:var(--cream)}
175.dark .h2,.dark .display{color:var(--cream)}
176.dark .sec-head p,.dark .lead{color:var(--cream-mute)}
177.dark .eyebrow{color:var(--ember-soft)}.dark .eyebrow::before{background:var(--ember-soft)}
178
179/* Who-it's-for */
180.who{text-align:center;background:var(--paper-pure)}
180
181.who .wrap{max-width:900px}
182.who .eyebrow{justify-content:center}
183.who .h2{margin:.4em 0 .55em;font-size:clamp(2.3rem,6vw,5rem)}
184.who p{font-size:clamp(1.1rem,1.6vw,1.45rem);color:var(--mute);max-width:50ch;margin-inline:auto;line-height:1.6}
185.who .rule{width:1px;height:60px;background:linear-gradient(var(--ember),transparent);margin:0 auto clamp(36px,5vh,60px)}
186
187/* Steps */
188.steps{display:grid;grid-template-columns:repeat(3,1fr);gap:clamp(20px,3vw,40px)}
189.step{padding-top:30px;border-top:1px solid var(--line)}
190.dark .step{border-color:var(--line-cream)}
191.step .no{font-family:var(--font-display);font-style:italic;font-size:2.6rem;color:var(--ember);line-height:1;margin-bottom:.4em}
192html[lang="zh"] .step .no{font-style:normal}
193.step h3{font-size:1.5rem;letter-spacing:-.025em;margin-bottom:.5em;font-weight:600}
194.step p{color:var(--mute);font-size:1rem;line-height:1.65;max-width:36ch}
195.dark .step p{color:var(--cream-mute)}
196
197/* Science cards */
198.cards{display:grid;grid-template-columns:repeat(3,1fr);gap:18px}
199.card{background:var(--paper-pure);border:1px solid var(--line);border-radius:var(--r-lg);padding:34px 30px 36px;transition:transform .5s var(--ease),box-shadow .5s var(--ease),border-color .5s}
200.dark .card{background:rgba(239,231,216,.035);border-color:var(--line-cream)}
201.card:hover{transform:translateY(-6px);box-shadow:var(--shadow-soft);border-color:var(--line-strong)}
202.card .ic{width:46px;height:46px;border-radius:13px;display:grid;place-items:center;background:var(--ember-glow);color:var(--ember);margin-bottom:22px}
203.card h3{font-size:1.32rem;letter-spacing:-.025em;margin-bottom:.5em;font-weight:600}
204.card p{color:var(--mute);font-size:.97rem;line-height:1.62}
205.dark .card p{color:var(--cream-mute)}
206.card .tag{margin-top:18px;font-family:var(--font-mono);font-size:.66rem;letter-spacing:.16em;text-transform:uppercase;color:var(--faint)}
207
208/* Feature rows */
209.feat{display:grid;grid-template-columns:1fr 1fr;gap:clamp(36px,6vw,96px);align-items:center;padding:clamp(50px,8vh,96px) 0}
210.feat:nth-child(even) .feat-media{order:-1}
211.feat-copy h3{font-size:clamp(1.7rem,3vw,2.7rem);letter-spacing:-.035em;line-height:1.05;margin-bottom:.5em}
212html[lang="zh"] .feat-copy h3{letter-spacing:-.01em;line-height:1.2}
213.feat-copy p{color:var(--mute);font-size:1.08rem;line-height:1.65;max-width:42ch}
214.feat-media{aspect-ratio:4/3;border-radius:var(--r-lg);border:1px solid var(--line);overflow:hidden;position:relative;box-shadow:var(--shadow-soft);background:var(--paper-pure)}
215.viz{position:absolute;inset:0;display:grid;place-items:center;padding:30px}
216.viz-1{background:linear-gradient(135deg,#fffdf9,#efe7d8)}
217.paper-chip{background:#fff;border:1px solid var(--line);border-radius:14px;padding:18px 20px;box-shadow:var(--shadow-soft);transform:rotate(-3deg);font-size:.82rem;color:var(--mute);max-width:78%}
218.paper-chip .arrow{display:block;text-align:center;color:var(--ember);font-size:1.4rem;margin:10px 0}
219.scene-chip{background:#fff;border:1px solid var(--line-strong);border-radius:14px;padding:14px 16px;box-shadow:var(--shadow-lift);transform:rotate(2deg);margin-top:-44px;margin-left:30%}
220.scene-chip .who{font-family:var(--font-mono);font-size:.56rem;letter-spacing:.16em;text-transform:uppercase;color:var(--ember)}
221.scene-chip .t{font-size:.92rem;color:var(--ink);font-weight:500;margin-top:4px}
222.viz-2{background:radial-gradient(100% 100% at 30% 20%,#16120e,#0d0b09)}
223.queue{display:flex;flex-direction:column;gap:11px;width:78%}
224.q-row{display:flex;align-items:center;gap:12px;background:rgba(239,231,216,.05);border:1px solid var(--line-cream);border-radius:12px;padding:11px 14px}
225.q-row .d{width:8px;height:8px;border-radius:50%;background:var(--ember);flex:none}
226.q-row .d.s{background:#3aa06a}
227.q-row .tx{font-size:.8rem;color:var(--cream);flex:1;font-family:"Manrope",sans-serif}
228.q-row .due{font-family:var(--font-mono);font-size:.64rem;color:var(--cream-mute);white-space:nowrap}
229.viz-3{background:linear-gradient(135deg,#fffdf9,#f1e9da)}
230.compare-bars{display:flex;gap:18px;align-items:flex-end;width:80%;justify-content:center}
231.compare-bars .col{flex:1;text-align:center}
232.compare-bars .stack{height:120px;
232display:flex;align-items:flex-end;gap:4px;justify-content:center}
233.compare-bars .stack i{width:6px;border-radius:3px;background:var(--ember-soft)}
234.compare-bars .col.now .stack i{background:var(--ember)}
235.compare-bars .cl{font-family:var(--font-mono);font-size:.6rem;letter-spacing:.14em;text-transform:uppercase;color:var(--mute);margin-top:10px}
236
237/* Comparison */
238.compare-grid{display:grid;grid-template-columns:repeat(3,1fr);gap:18px;align-items:stretch}
239.cmp{background:var(--paper-pure);border:1px solid var(--line);border-radius:var(--r-lg);padding:32px 28px 34px;display:flex;flex-direction:column}
240.cmp.featured{border-color:var(--ember);box-shadow:var(--shadow-ember);position:relative}
241.cmp.featured::before{content:attr(data-badge);position:absolute;top:-12px;left:28px;background:var(--ember);color:#fff;font-family:var(--font-mono);font-size:.6rem;letter-spacing:.14em;text-transform:uppercase;padding:5px 11px;border-radius:999px}
242.cmp h4{font-size:1.18rem;font-weight:700;letter-spacing:-.02em;margin-bottom:1.2em;color:var(--ink-soft)}
243.cmp.featured h4{color:var(--ember)}
244.cmp ul{list-style:none;display:flex;flex-direction:column;gap:16px;flex:1}
245.cmp li{display:flex;gap:11px;font-size:.95rem;line-height:1.5;color:var(--mute)}
246.cmp li .ck{flex:none;width:18px;height:18px;margin-top:2px;color:var(--faint)}
247.cmp.featured li{color:var(--ink-soft)}
248.cmp.featured li .ck{color:var(--ember)}
249.cmp li .lab{display:block;font-family:var(--font-mono);font-size:.58rem;letter-spacing:.14em;text-transform:uppercase;color:var(--faint);margin-bottom:3px}
250html[lang="zh"] .cmp li .lab{letter-spacing:.1em}
251
252/* Pull quote */
253.quote{text-align:center;background:var(--paper)}
254.quote .wrap{max-width:1000px}
255.quote blockquote{font-family:var(--font-display);font-style:italic;font-size:clamp(1.9rem,4.6vw,3.6rem);line-height:1.22;letter-spacing:-.02em;color:var(--ink)}
256html[lang="zh"] .quote blockquote{font-style:normal;line-height:1.4;letter-spacing:0}
257.quote blockquote em{color:var(--ember);font-style:italic}
258html[lang="zh"] .quote blockquote em{font-style:normal}
259.quote .qmark{font-family:var(--font-display);font-size:5rem;color:var(--ember-soft);line-height:.5;margin-bottom:.2em;display:block}
260.quote .attr{margin-top:1.6em;font-family:var(--font-mono);font-size:.74rem;letter-spacing:.2em;text-transform:uppercase;color:var(--faint)}
261
262/* Stats */
263.stats{display:grid;grid-template-columns:repeat(4,1fr);gap:24px}
264.stat{padding:26px 0;border-top:1px solid var(--line-cream)}
265.stat .n{font-family:var(--font-display);font-size:clamp(2.6rem,5vw,4rem);line-height:1;color:var(--cream);letter-spacing:-.02em}
266.stat .n em{font-style:italic;color:var(--ember-soft)}
267.stat .l{margin-top:.6em;font-size:.9rem;color:var(--cream-mute);max-width:26ch;line-height:1.5}
268
269/* FAQ */
270.faq-list{max-width:820px;border-top:1px solid var(--line)}
271.faq-item{border-bottom:1px solid var(--line)}
272.faq-q{width:100%;background:none;border:none;cursor:pointer;display:flex;align-items:center;justify-content:space-between;gap:24px;padding:26px 4px;text-align:left;font-family:var(--font-sans);font-size:clamp(1.05rem,1.6vw,1.3rem);font-weight:600;color:var(--ink);letter-spacing:-.02em}
273html[lang="zh"] .faq-q{letter-spacing:0}
274.faq-q .pm{flex:none;width:24px;height:24px;position:relative;transition:transform .4s var(--ease)}
275.faq-q .pm::before,.faq-q .pm::after{content:"";position:absolute;background:var(--ember);border-radius:2px;top:50%;left:50%;transform:translate(-50%,-50%)}
276.faq-q .pm::before{width:15px;height:2px}
277.faq-q .pm::after{width:2px;height:15px;transition:transform .4s var(--ease)}
278.faq-item.open .pm{transform:rotate(180deg)}
279.faq-item.open .pm::after{transform:translate(-50%,-50%) scaleY(0)}
280.faq-a{display:grid;grid-template-rows:0fr;transition:grid-template-rows .5s var(--ease)}
281.faq-item.open .faq-a{grid-template-rows:1fr}
282.faq-a>div{overflow:hidden}
283.faq-a p{padding:0 4px 28px;max-width:62ch;color:var(--mute);font-size:1.02rem;line-height:1.65}
284
285/* CTA */
286.cta{padding:clamp(110px,18vh,200px) 0;text-align:center;position:relative;overflow:hidden}
287.cta-bg{position:absolute;inset:0;background:radial-gradient(60% 70% at 50% 30%,var(--ember-glow),transparent 70%);z-index:0}
288.cta .inner{position:relative;z-index:1}
289.cta .big{font-size:clamp(2.2rem,5vw,4.6rem);letter-spacing:-.025em;line-height:1.08;margin-bottom:.5em}
290html[lang="zh"] .cta .big{letter-spacing:-.01em;line-height:1.12}
291.cta .big em{font-family:var(--font-display);font-style:italic;font-weight:400;color:var(--ember)}
292html[lang="zh"] .cta .big em{font-style:normal}
293.cta .lead{margin:0 auto 2em;text-align:center;max-width:46ch}
294.cta .hero-cta{justify-content:center}
295
296/* Footer */
297footer{background:var(--night);color:var(--cream-mute);padding:70px 0 50px;border-top:1px solid var(--line-cream)}
298.foot{display:flex;justify-content:space-between;gap:40px;flex-wrap:wrap;align-items:flex-start}
299.foot .brand{color:var(--cream);font-size:1.3rem}
300.foot p.tg{max-width:32ch;font-size:.92rem;margin-top:1em;line-height:1.6}
301.foot-cols{display:flex;gap:64px;flex-wrap:wrap}
302.foot-col h5{font-family:var(--font-mono);font-size:.66rem;letter-spacing:.2em;text-transform:uppercase;color:var(--faint);margin-bottom:1.1em}
303.foot-col a{display:block;font-size:.92rem;margin-bottom:.7em;color:var(--cream-mute);transition:color .2s}
304.foot-col a:hover{color:var(--ember-soft)}
305.foot-base{margin-top:54px;padding-top:26px;border-top:1px solid var(--line-cream);display:flex;justify-content:space-between;gap:20px;flex-wrap:wrap;font-size:.8rem;color:var(--faint)}
306
307/* Reveal */
308.reveal{opacity:0;transform:translateY(28px);transition:opacity 1s var(--ease),transform 1s var(--ease)}
309.reveal.in{opacity:1;transform:none}
310.reveal.d1{transition-delay:.08s}.reveal.d2{transition-delay:.16s}.reveal.d3{transition-delay:.24s}
311html:not(.js) .reveal{opacity:1;transform:none}
312@media (prefers-reduced-motion:reduce){
313  .reveal{opacity:1!important;transform:none!important;transition:none}
314  .js .hero .rise{animation:none;opacity:1;transform:none}
315  .js .device,.js .wave i,.js .rec::after,.js .marquee .row{animation:none}
316}
317
318/* Responsive */
319@media (max-width:900px){
320  .hero-grid{grid-template-columns:1fr;text-align:center}
321  .hero-copy{max-width:none;margin-inline:auto}
322  .hero .lead{margin-inline:auto}
323  .hero-cta,.hero-note{justify-content:center}
324  .device{margin-top:50px}
325  .steps,.cards,.stats,.compare-grid{grid-template-columns:1fr}
326  .feat{grid-template-columns:1fr;gap:32px}
327  .feat:nth-child(even) .feat-media{order:0}
328  .stats{gap:0}
329}
330@media (max-width:560px){
331  .nav-cta .btn-ghost{display:none}
332  .marquee .lbl{font-size:1.5rem}
333  .lang-toggle button{padding:.3em .6em}
334}
335
336/* ============================================================
337   Added motion layer
338   ============================================================ */
339.scroll-progress{position:fixed;top:0;left:0;height:2px;width:0;background:linear-gradient(90deg,var(--ember),var(--ember-bright));z-index:200;transition:width .1s linear;pointer-events:none}
340.nav-links a.lnk{position:relative}
341.nav-links a.lnk::after{content:"";position:absolute;left:0;right:0;bottom:-5px;height:1.5px;background:var(--ember);transform:scaleX(0);transform-origin:left;transition:transform .35s var(--ease)}
342.nav-links a.lnk:hover::after{transform:scaleX(1)}
343.btn-primary{will-change:transform}
344.card{position:relative;overflow:hidden}
345.card::after{content:"";position:absolute;inset:0;border-radius:inherit;background:radial-gradient(240px circle at var(--mx,50%) var(--my,50%),var(--ember-glow),transparent 62%);opacity:0;transition:opacity .45s var(--ease);pointer-events:none}
346.card:hover::after{opacity:1}
347.cta-bg{background:radial-gradient(42% 52% at 28% 32%,var(--ember-glow),transparent 70%),radial-gradient(40% 50% at 72% 62%,rgba(223,142,54,.13),transparent 70%)}
348@media (prefers-reduced-motion:no-preference){.cta-bg{animation:mesh 18s ease-in-out infinite alternate}}
349@keyframes mesh{0%{background-position:0% 0%,100% 100%}100%{background-position:34% 22%,66% 78%}}
350.hero-bg{will-change:transform}
351
352/* ============================================================
353   Auth modal (Supabase) + checkout (Dodo)
354   ============================================================ */
355.auth-overlay{position:fixed;inset:0;z-index:300;display:flex;align-items:center;justify-content:center;padding:24px;background:rgba(13,11,9,.5);backdrop-filter:blur(8px);-webkit-backdrop-filter:blur(8px);opacity:0;transition:opacity .35s var(--ease)}
356.auth-overlay.open{opacity:1}
357.auth-overlay[hidden]{display:none}
358.auth-card{width:min(420px,100%);background:var(--paper-pure);border:1px solid var(--line);border-radius:var(--r-lg);box-shadow:var(--shadow-lift);padding:clamp(26px,4vw,40px);position:relative;transform:translateY(12px) scale(.98);transition:transform .4s var(--ease)}
359.auth-overlay.open .auth-card{transform:none}
360.auth-close{position:absolute;top:14px;right:16px;width:34px;height:34px;border:none;background:none;font-size:1.5rem;line-height:1;color:var(--mute);cursor:pointer;border-radius:50%;transition:background .2s,color .2s}
361.auth-close:hover{background:var(--paper);color:var(--ink)}
362.auth-brand{font-family:var(--font-display);font-weight:600;font-size:1.5rem;color:var(--ink);margin-bottom:4px}
363.auth-tabs{display:flex;gap:4px;background:var(--paper);border-radius:999px;padding:4px;margin:18px 0 22px}
364.auth-tabs button{flex:1;border:none;background:none;font-family:var(--font-sans);font-size:.9rem;font-weight:600;color:var(--mute);padding:.6em;border-radius:999px;cursor:pointer;transition:background .25s,color .25s}
365.auth-tabs button.active{background:var(--ink);color:var(--paper)}
366.auth-field{margin-bottom:14px}
367.auth-field label{display:block;font-family:var(--font-mono);font-size:.62rem;letter-spacing:.14em;text-transform:uppercase;color:var(--faint);margin-bottom:7px}
368.auth-field input{width:100%;font-family:var(--font-sans);font-size:1rem;color:var(--ink);background:var(--paper);border:1px solid var(--line-strong);border-radius:12px;padding:.85em 1em;transition:border-color .25s,box-shadow .25s}
369.auth-field input:focus{outline:none;border-color:var(--ember);box-shadow:0 0 0 3px var(--ember-glow)}
370.auth-card .btn{width:100%;justify-content:center;margin-top:6px}
371.auth-msg{font-size:.86rem;line-height:1.5;margin-top:14px;min-height:1.2em;color:var(--mute)}
372.auth-msg.error{color:var(--ember)}
373.auth-msg.ok{color:#3a8a5e}
374.auth-foot{margin-top:16px;font-size:.78rem;color:var(--faint);text-align:center;line-height:1.5}
375.auth-acct .auth-email{font-family:var(--font-mono);font-size:.9rem;color:var(--ink);background:var(--paper);border-radius:10px;padding:.7em 1em;margin:14px 0 18px;word-break:break-all}
376.auth-acct .sub-note{font-size:.82rem;color:var(--mute);margin:14px 0 0;line-height:1.55}
377</style>
378</head>
379<body>
380<div class="grain" aria-hidden="true"></div>
381<div class="scroll-progress" id="sprog" aria-hidden="true"></div>
382
383<!-- NAV -->
384<nav class="nav" id="nav">
385  <div class="nav-inner">
386    <a class="brand" href="#top" data-i18n-aria="nav.brandAria" aria-label="Lomia home">
387      <svg class="mark" viewBox="0 0 32 32" fill="none" aria-hidden="true">
388        <path d="M6 25V8.5C6 7 7 6 8.5 6H22c2 0 3.5 1.6 3.5 3.6 0 1.9-1.4 3.4-3.3 3.4" stroke="#df5a36" stroke-width="2.1" stroke-linecap="round"/>
389        <path d="M6 16h13.5c2.2 0 3.9 1.6 3.9 3.7 0 2.1-1.7 3.8-3.9 3.8H9.5" stroke="#df5a36" stroke-width="2.1" stroke-linecap="round"/>
390        <circle cx="24.5" cy="25" r="2.4" fill="#df5a36"/>
391      </svg>
392      <b>Lomia</b>
393    </a>
394    <div class="nav-links">
395      <a class="lnk" href="#how" data-i18n="nav.how">How it works</a>
396      <a class="lnk" href="#science" data-i18n="nav.why">Why it works</a>
397      <a class="lnk" href="#compare" data-i18n="nav.features">Features</a>
398      <a class="lnk" href="#faq" data-i18n="nav.faq">FAQ</a>
399    </div>
400    <div class="nav-cta">
401      <div class="lang-toggle" role="group" aria-label="Language">
402        <button type="button" data-lang="en">EN</button>
403        <button type="button" data-lang="zh">中</button>
404      </div>
405      <a class="btn btn-ghost" href="#cta" data-i18n="nav.signin">Sign in</a>
406      <a class="btn btn-primary" href="#cta" data-i18n="nav.start">Start free</a>
407    </div>
408  </div>
409</nav>
410
411<!-- HERO -->
412<header class="hero" id="top">
413  <div class="hero-bg"></div>
414  <div class="wrap hero-grid">
415    <div class="hero-copy">
416      <div class="hero-yan rise d1" id="yanHero" aria-hidden="true" style="width:62px;margin:0 0 18px"></div>
417      <span class="eyebrow rise d1" data-i18n="hero.eyebrow">Language, out loud</span>
418      <h1 class="display rise d2" data-i18n="hero.h1"><span class="l1">Don't just read</span><span class="l1">a language.</span><span class="l2"><em class="acc">Live it out loud.</em></span></h1>
419      <p class="lead rise d3" data-i18n="hero.lead">Lomia turns anything you read into spoken story episodes — then listens as you say each line back, and brings the words you stumble on right back to you.</p>
420      <div class="hero-cta rise d4">
421        <a class="btn btn-primary" href="#cta" data-i18n="hero.cta1">Start with a story</a>
422        <a class="btn btn-ghost" href="#how" data-i18n="hero.cta2">See how it works <span class="arr">↓</span></a>
423      </div>
424      <div class="hero-note rise d5"><span class="dot"></span> <span data-i18n="hero.note">No account needed to try · Works with any text you bring</span></div>
425    </div>
426
427    <div class="device rise d4" id="device" aria-hidden="true">
428      <div class="dev-top">
429        <span class="scene" data-i18n="device.scene">Aesop · Tortoise &amp; Hare · 3</span>
430        <div class="score-ring" style="--v:92"><b>92</b></div>
431      </div>
432      <div class="bubble">“Slow and steady — is that really how you win?”</div>
433      <div class="line-card">
434        <div class="who" data-i18n="device.you">You · say it back</div>
435        <div class="txt">The tortoise <span class="w-now">plodded</span> on, slow <span class="w-miss">and</span> steady.</div>
436        <div class="wave">
437          <i style="animation-delay:0s"></i>
437<i style="animation-delay:.08s"></i><i style="animation-delay:.16s"></i><i style="animation-delay:.24s"></i><i style="animation-delay:.32s"></i><i style="animation-delay:.4s"></i><i style="animation-delay:.48s"></i><i style="animation-delay:.56s"></i><i style="animation-delay:.64s"></i><i style="animation-delay:.72s"></i><i style="animation-delay:.6s"></i><i style="animation-delay:.5s"></i><i style="animation-delay:.4s"></i><i style="animation-delay:.3s"></i><i style="animation-delay:.2s"></i>
438        </div>
439        <div class="metrics">
440          <div class="metric"><div class="ml"><span data-i18n="metric.accuracy">Accuracy</span><b>96</b></div><div class="bar"><span style="width:96%"></span></div></div>
441          <div class="metric jade"><div class="ml"><span data-i18n="metric.fluency">Fluency</span><b>90</b></div><div class="bar"><span style="width:90%"></span></div></div>
442          <div class="metric"><div class="ml"><span data-i18n="metric.completeness">Completeness</span><b>88</b></div><div class="bar"><span style="width:88%"></span></div></div>
443          <div class="metric jade"><div class="ml"><span data-i18n="metric.prosody">Prosody</span><b>94</b></div><div class="bar"><span style="width:94%"></span></div></div>
444        </div>
445      </div>
446      <div class="dev-actions">
447        <div class="ghost-btn">↺</div>
448        <button class="rec" data-i18n-aria="device.recAria" aria-label="Record"><span></span></button>
449        <div class="ghost-btn">▶</div>
450      </div>
451    </div>
452  </div>
453</header>
454
455<!-- MARQUEE -->
456<div class="marquee">
457  <div class="marquee-cap" data-i18n="marquee.cap">Bring anything you actually read</div>
458  <div class="row" id="marquee-row" aria-hidden="true"></div>
459</div>
460
461<!-- WHO -->
462<section class="sec who">
463  <div class="wrap">
464    <div class="rule reveal" aria-hidden="true"></div>
465    <span class="eyebrow reveal" data-i18n="who.eyebrow">Sound familiar?</span>
466    <h2 class="h2 reveal d1" data-i18n="who.h2">You can read it.<br>You just can't <em class="acc">say</em> it.</h2>
467    <p class="reveal d2" data-i18n="who.p">You've passed the exams. You understand the films. But the moment you need to speak, the words don't come. Lomia is built for exactly that gap — between knowing a language and living inside it.</p>
468  </div>
469</section>
470
471<!-- HOW -->
472<section class="sec" id="how" style="background:var(--paper)">
473  <div class="wrap">
474    <div class="sec-head reveal">
475      <span class="eyebrow" data-i18n="how.eyebrow">How it works</span>
476      <h2 class="h2" data-i18n="how.h2">Three steps from<br>a page to your voice.</h2>
477      <p data-i18n="how.p">No decks of flashcards. No grammar drills. Just the loop that actually builds spoken language — listen, say it back, and hear what to fix.</p>
478    </div>
479    <div class="steps">
480      <div class="step reveal d1"><div class="no">01</div><h3 data-i18n="how.s1t">Paste anything</h3><p data-i18n="how.s1p">An article, a chapter, a transcript, or a fable. The language you actually want to speak — not a textbook's idea of it.</p></div>
481      <div class="step reveal d2"><div class="no">02</div><h3 data-i18n="how.s2t">It becomes a story</h3><p data-i18n="how.s2p">Lomia rewrites it into short, two-voice scenes pitched at your level — natural lines worth saying out loud.</p></div>
482      <div class="step reveal d3"><div class="no">03</div><h3 data-i18n="how.s3t">Shadow it</h3><p data-i18n="how.s3p">Speak each line over a native voice. Hear yourself instantly, and see exactly which words and rhythms to chase.</p></div>
483    </div>
484  </div>
485</section>
486
487<!-- DIAGNOSIS (dark) -->
488<section class="sec dark on-dark">
489  <div class="wrap">
490    <div class="sec-head reveal" style="margin-inline:auto;text-align:center">
491      <span class="eyebrow" style="justify-content:center" data-i18n="diag.eyebrow">Your voice, measured</span>
492      <h2 class="h2" data-i18n="diag.h2">Not a grade. A diagnosis.</h2>
493      <p style="margin-inline:auto" data-i18n="diag.p">Every take is broken into the things that actually make you understood — so a low score always comes with a reason, and a way to fix it.</p>
494    </div>
495    <div class="cards">
496      <div class="card reveal d1">
497        <div class="ic"><svg width="22" height="22" viewBox="0 0 24 24" fill="none"><path d="M12 3v18M7 8v8M17 7v10M3 11v2M21 10v4" stroke="currentColor" stroke-width="2" stroke-linecap="round"/></svg></div>
498        <h3 data-i18n="diag.c1t">Words &amp; sounds</h3>
499        <p data-i18n="diag.c1p">Edit-distance alignment matches what you said to the line — so one extra word never red-marks the rest. The sounds Mandarin speakers find hardest get singled out, gently.</p>
500        <div class="tag" data-i18n="diag.c1tag">Accuracy · Completeness</div>
501      </div>
502      <div class="card reveal d2">
503        <div class="ic"><svg width="22" height="22" viewBox="0 0 24 24" fill="none"><path d="M4 18c3-8 5-12 8-12s5 4 8 12" stroke="currentColor" stroke-width="2" stroke-linecap="round"/>
503<path d="M3 21h18" stroke="currentColor" stroke-width="2" stroke-linecap="round"/></svg></div>
504        <h3 data-i18n="diag.c2t">Rhythm &amp; melody</h3>
505        <p data-i18n="diag.c2p">Lomia scores your pace, pauses and pitch against the original. Because being understood is mostly prosody — the music of a sentence — not perfect phonemes.</p>
506        <div class="tag" data-i18n="diag.c2tag">Fluency · Prosody</div>
507      </div>
508      <div class="card reveal d3">
509        <div class="ic"><svg width="22" height="22" viewBox="0 0 24 24" fill="none"><path d="M21 12a9 9 0 1 1-3-6.7" stroke="currentColor" stroke-width="2" stroke-linecap="round"/><path d="M21 4v5h-5" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/></svg></div>
510        <h3 data-i18n="diag.c3t">The words come back</h3>
511        <p data-i18n="diag.c3p">Every line you stumble on becomes a memory the app tracks — and resurfaces right before you'd forget it, on a schedule tuned to your own curve.</p>
512        <div class="tag" data-i18n="diag.c3tag">Spaced review</div>
513      </div>
514    </div>
515  </div>
516</section>
517
518<!-- SCIENCE / FEATURES -->
519<section class="sec" id="science" style="background:var(--paper)">
520  <div class="wrap">
521    <div class="sec-head reveal">
522      <span class="eyebrow" data-i18n="ev.eyebrow">Built on the evidence</span>
523      <h2 class="h2" data-i18n="ev.h2">Designed around how<br>language actually sticks.</h2>
524      <p data-i18n="ev.p">Every choice in Lomia traces to second-language research — comprehensible input, spaced retrieval, and the things shadowing is proven to train.</p>
525    </div>
526  </div>
527  <div class="wrap" id="features">
528    <div class="feat reveal">
529      <div class="feat-copy"><h3 data-i18n="f1t">Any text becomes <em class="acc">a scene.</em></h3><p data-i18n="f1p">Paste a paragraph and watch it turn into a short conversation between two voices — rewritten to your level, never above it. The sweet spot, just past what you already know, where new language takes hold.</p></div>
530      <div class="feat-media"><div class="viz viz-1"><div style="width:100%"><div class="paper-chip" data-i18n="f1viz1">“The annual report showed a marked decline in regional output, attributable to…”<span class="arrow">↓</span></div><div class="scene-chip"><div class="who" data-i18n="f1viz2">Role A · curious</div><div class="t">“So… the numbers dropped. Why?”</div></div></div></div></div>
531    </div>
532    <div class="feat reveal">
533      <div class="feat-copy"><h3 data-i18n="f2t">The words <em class="acc">come back.</em></h3><p data-i18n="f2p">No streaks to protect, no guilt for a missed day. Lomia simply remembers the lines and words you struggled with, and quietly puts them in front of you again at exactly the right moment.</p></div>
534      <div class="feat-media"><div class="viz viz-2"><div class="queue">
535        <div class="q-row"><span class="d"></span><span class="tx">“plodded on, slow and steady”</span><span class="due" data-i18n="due.now">due now</span></div>
536        <div class="q-row"><span class="d"></span><span class="tx">“attributable to a shortfall”</span><span class="due" data-i18n="due.d2">in 2d</span></div>
537        <div class="q-row"><span class="d s"></span><span class="tx">“the door was still open”</span><span class="due" data-i18n="due.d9">in 9d</span></div>
538        <div class="q-row"><span class="d s"></span><span class="tx">“whether or not it mattered”</span><span class="due" data-i18n="due.d21">in 21d</span></div>
539      </div></div></div>
540    </div>
541    <div class="feat reveal">
542      <div class="feat-copy"><h3 data-i18n="f3t">Hear your <em class="acc">past self.</em></h3><p data-i18n="f3p">Lomia keeps your takes. A month in, set today's voice beside the one from week one on the same line — the kind of progress you can actually hear, not just a number going up.</p></div>
543      <div class="feat-media"><div class="viz viz-3"><div class="compare-bars">
544        <div class="col then"><div class="stack"><i style="height:30%"></i><i style="height:55%"></i><i style="height:22%"></i><i style="height:60%"></i><i style="height:18%"></i><i style="height:45%"></i></div><div class="cl" data-i18n="cmp.then">4 weeks ago</div></div>
545        <div class="col now"><div class="stack"><i style="height:62%"></i><i style="height:90%"></i><i style="height:70%"></i><i style="height:96%"></i><i style="height:58%"></i><i style="height:84%"></i></div><div class="cl" data-i18n="cmp.now">today</div></div>
546      </div></div></div>
547    </div>
548  </div>
549</section>
550
551<!-- COMPARISON -->
552<section class="sec" id="compare" style="background:var(--paper-pure)">
553  <div class="wrap">
554    <div class="sec-head reveal">
555      <span class="eyebrow" data-i18n="cm.eyebrow">Why not just…</span>
556      <h2 class="h2" data-i18n="cm.h2">Three ways to study.<br>Only one makes you <em class="acc">speak.</em></h2>
557    </div>
558    <div class="compare-grid">
559      <div class="cmp reveal d1">
560        <h4 data-i18n="cm.aT">Flashcard apps</h4>
561        <ul>
562          <li><svg class="ck" width="18" height="18" viewBox="0 0 24 24" fill="none"><circle cx="12" cy="12" r="9" stroke="currentColor" stroke-width="2"/></svg><span><span class="lab" data-i18n="cm.r1">You do</span><span data-i18n="cm.a1">Memorize words in isolation</span></span></li>
563          <li><svg class="ck" width="18" height="18" viewBox="0 0 24 24" fill="none"><circle cx="12" cy="12" r="9" stroke="currentColor" stroke-width="2"/></svg><span><span class="lab" data-i18n="cm.r2">Measured by</span><span data-i18n="cm.a2">Tapping that you recognize it</span></span></li>
564          <li><svg class="ck" width="18" height="18" viewBox="0 0 24 24" fill="none"><circle cx="12" cy="12" r="9" stroke="currentColor" stroke-width="2"/></svg><span><span class="lab" data-i18n="cm.r3">You walk away</span><span data-i18n="cm.a3">Able to recognize — not produce</span></span></li>
565        </ul>
566      </div>
567      <div class="cmp reveal d2">
568        <h4 data-i18n="cm.bT">Streak apps</h4>
569        <ul>
570          <li><svg class="ck" width="18" height="18" viewBox="0 0 24 24" fill="none"><circle cx="12" cy="12" r="9" stroke="currentColor" stroke-width="2"/></svg><span><span class="lab" data-i18n="cm.r1">You do</span><span data-i18n="cm.b1">Protect a daily streak</span></span></li>
571          <li><svg class="ck" width="18" height="18" viewBox="0 0 24 24" fill="none"><circle cx="12" cy="12" r="9" stroke="currentColor" stroke-width="2"/></svg><span><span class="lab" data-i18n="cm.r2">Measured by</span><span data-i18n="cm.b2">Logging in, day after day</span></span></li>
572          <li><svg class="ck" width="18" height="18" viewBox="0 0 24 24" fill="none"><circle cx="12" cy="12" r="9" stroke="currentColor" stroke-width="2"/></svg><span><span class="lab" data-i18n="cm.r3">You walk away</span><span data-i18n="cm.b3">A habit of tapping — and guilt</span></span></li>
573        </ul>
574      </div>
575      <div class="cmp featured reveal d3" data-badge="Lomia" data-i18n-badge="cm.badge">
576        <h4 data-i18n="cm.cT">Lomia</h4>
577        <ul>
578          <li><svg class="ck" width="18" height="18" viewBox="0 0 24 24" fill="none"><path d="M5 13l4 4L19 7" stroke="currentColor" stroke-width="2.4" stroke-linecap="round" stroke-linejoin="round"/></svg><span><span class="lab" data-i18n="cm.r1">You do</span><span data-i18n="cm.c1">Speak real sentences out loud</span></span></li>
579          <li><svg class="ck" width="18" height="18" viewBox="0 0 24 24" fill="none"><path d="M5 13l4 4L19 7" stroke="currentColor" stroke-width="2.4" stroke-linecap="round" stroke-linejoin="round"/></svg><span><span class="lab" data-i18n="cm.r2">Measured by</span><span data-i18n="cm.c2">Four dimensions of being understood</span></span></li>
580          <li><svg class="ck" width="18" height="18" viewBox="0 0 24 24" fill="none"><path d="M5 13l4 4L19 7" stroke="currentColor" stroke-width="2.4" stroke-linecap="round" stroke-linejoin="round"/></svg><span><span class="lab" data-i18n="cm.r3">You walk away</span><span data-i18n="cm.c3">Actually able to speak — nothing to break</span></span></li>
581        </ul>
582      </div>
583    </div>
584  </div>
585</section>
586
587<!-- QUOTE -->
588<section class="sec quote">
589  <div class="wrap">
590    <span class="qmark reveal" aria-hidden="true">“</span>
591    <blockquote class="reveal d1" data-i18n="quote.text">A language you can't speak isn't yours yet.<br>It's time to make it <em>yours</em> — out loud.</blockquote>
592    <div class="attr reveal d2" data-i18n="quote.attr">— The idea behind Lomia</div>
593  </div>
594</section>
595
596<!-- STATS (dark) -->
597<section class="sec dark on-dark">
598  <div class="wrap">
599    <div class="sec-head reveal" style="max-width:700px">
600      <span class="eyebrow" data-i18n="st.eyebrow">For the quietly fluent</span>
601      <h2 class="h2" data-i18n="st.h2">You've studied for years.<br>Time to be heard.</h2>
602    </div>
603    <div class="stats">
604      <div class="stat reveal d1"><div class="n"><span data-count="4">4</span><em>×</em></div><div class="l" data-i18n="st.l1">dimensions scored on every line, not one opaque grade</div></div>
605      <div class="stat reveal d1"><div class="n"><em>∞</em></div><div class="l" data-i18n="st.l2">sources — any text you bring becomes practice</div></div>
606      <div class="stat reveal d2"><div class="n">0</div><div class="l" data-i18n="st.l3">streaks, hearts, or guilt — progress, not pressure</div></div>
607      <div class="stat reveal d3"><div class="n">1<em>:</em>1</div><div class="l" data-i18n="st.l4">your voice against a native line, every single time</div></div>
608    </div>
609  </div>
610</section>
611
612<!-- FAQ -->
613<section class="sec" id="faq" style="background:var(--paper)">
614  <div class="wrap">
615    <div class="sec-head reveal"><span class="eyebrow" data-i18n="faq.eyebrow">Questions</span><h2 class="h2" data-i18n="faq.h2">Good to know.</h2></div>
616    <div class="faq-list reveal d1">
617      <div class="faq-item"><button class="faq-q" aria-expanded="false"><span data-i18n="faq.q1">
617What can I put into Lomia?</span><span class="pm" aria-hidden="true"></span></button><div class="faq-a"><div><p data-i18n="faq.a1">Anything you read in your target language — an article, a book chapter, a podcast transcript, song lyrics, even a work email. Lomia turns it into short spoken scenes at your level.</p></div></div></div>
618      <div class="faq-item"><button class="faq-q" aria-expanded="false"><span data-i18n="faq.q2">Do I need a microphone?</span><span class="pm" aria-hidden="true"></span></button><div class="faq-a"><div><p data-i18n="faq.a2">Yes — speaking is the whole point. Lomia listens to each line you say and shows you exactly what to improve, then brings the hard ones back later.</p></div></div></div>
619      <div class="faq-item"><button class="faq-q" aria-expanded="false"><span data-i18n="faq.q3">Is this like Duolingo?</span><span class="pm" aria-hidden="true"></span></button><div class="faq-a"><div><p data-i18n="faq.a3">No. There are no streaks to lose and no guilt for a missed day. Lomia measures whether you can be understood, and resurfaces the words you struggle with on a schedule tuned to your own memory.</p></div></div></div>
620      <div class="faq-item"><button class="faq-q" aria-expanded="false"><span data-i18n="faq.q4">Which languages?</span><span class="pm" aria-hidden="true"></span></button><div class="faq-a"><div><p data-i18n="faq.a4">Lomia starts with English for Mandarin speakers — the gap we know best — with more language pairs on the way.</p></div></div></div>
621      <div class="faq-item"><button class="faq-q" aria-expanded="false"><span data-i18n="faq.q5">How fast can I start?</span><span class="pm" aria-hidden="true"></span></button><div class="faq-a"><div><p data-i18n="faq.a5">Under a minute. Paste a page, pick a voice, and you're speaking your first line.</p></div></div></div>
622    </div>
623  </div>
624</section>
625
626<!-- CTA -->
627<section class="cta" id="cta">
628  <div class="cta-bg" aria-hidden="true"></div>
629  <div class="wrap inner">
630    <h2 class="big reveal" data-i18n="cta.h2">Open a book.<br><em>Start a conversation.</em></h2>
631    <p class="lead reveal d1" data-i18n="cta.lead">Bring a page you've always meant to understand. In a minute, you'll be speaking it.</p>
632    <div class="hero-cta reveal d2">
633      <a class="btn btn-primary" href="#top" data-i18n="cta.b1">Start with a story</a>
634      <a class="btn btn-ghost" href="#how" data-i18n="cta.b2">How it works <span class="arr">↓</span></a>
635    </div>
636  </div>
637</section>
638
639<!-- FOOTER -->
640<footer>
641  <div class="wrap foot">
642    <div>
643      <a class="brand" href="#top">
644        <svg class="mark" viewBox="0 0 32 32" fill="none" aria-hidden="true"><path d="M6 25V8.5C6 7 7 6 8.5 6H22c2 0 3.5 1.6 3.5 3.6 0 1.9-1.4 3.4-3.3 3.4" stroke="#efe7d8" stroke-width="2.1" stroke-linecap="round"/><path d="M6 16h13.5c2.2 0 3.9 1.6 3.9 3.7 0 2.1-1.7 3.8-3.9 3.8H9.5" stroke="#efe7d8" stroke-width="2.1" stroke-linecap="round"/><circle cx="24.5" cy="25" r="2.4" fill="#df5a36"/></svg>
645        <b style="font-family:'Fraunces','Cormorant Garamond',serif;font-weight:600;font-size:1.55rem">Lomia</b>
646      </a>
647      <p class="tg" data-i18n="footer.tg">For people who learned a language for years — and still don't speak it.</p>
648    </div>
649    <div class="foot-cols">
650      <div class="foot-col"><h5 data-i18n="footer.c1">Product</h5><a href="#how" data-i18n="nav.how">How it works</a><a href="#science" data-i18n="footer.science">The science</a><a href="#compare" data-i18n="nav.features">Features</a><a href="#cta" data-i18n="nav.start">Start free</a></div>
651      <div class="foot-col"><h5 data-i18n="footer.c2">Stories</h5><a href="#" data-i18n="footer.s1">Aesop's fables</a><a href="#" data-i18n="footer.s2">Bring your own</a><a href="#" data-i18n="footer.s3">News &amp; articles</a><a href="#" data-i18n="footer.s4">Transcripts</a></div>
652      <div class="foot-col"><h5 data-i18n="footer.c3">Company</h5><a href="#" data-i18n="footer.co1">About</a><a href="#" data-i18n="footer.co2">Privacy</a><a href="#" data-i18n="footer.co3">Contact</a></div>
653    </div>
654  </div>
655  <div class="wrap foot-base"><span data-i18n="footer.base1">© 2026 Lomia. Speak the story.</span><span data-i18n="footer.base2">Made for the quietly fluent.</span></div>
656</footer>
657
658<!-- ============ AUTH MODAL (Supabase) ============ -->
659<div class="auth-overlay" id="authOverlay" hidden>
660  <div class="auth-card" role="dialog" aria-modal="true" aria-label="Lomia account">
661    <button class="auth-close" id="authClose" aria-label="Close">&times;</button>
662    <div class="auth-brand">Lomia</div>
663
664    <!-- sign in / sign up -->
665    <div id="authForm">
666      <div class="auth-tabs">
667        <button type="button" data-mode="signup" class="active" data-i18n="auth.tabSignup">Create account</button>
668        <button type="button" data-mode="signin" data-i18n="auth.tabSignin">Sign in</button>
669      </div>
670      <form id="authFormEl">
671        <div class="auth-field">
672          <label data-i18n="auth.emailLabel">Email</label>
673          <input id="authEmail" type="email" autocomplete="email" required placeholder="[email protected]" />
674        </div>
675        <div class="auth-field">
676          <label data-i18n="auth.passLabel">Password</label>
677          <input id="authPass" type="password" autocomplete="current-password" required minlength="6" placeholder="••••••••" />
678        </div>
679        <button class="btn btn-primary" id="authSubmit" type="submit" data-i18n="auth.submitSignup">Create account</button>
680      </form>
681      <p class="auth-msg" id="authMsg" role="status"></p>
682      <p class="auth-foot" data-i18n="auth.foot">No spam — just your spoken-language practice.</p>
683    </div>
684
685    <!-- signed-in account -->
686    <div id="authAccount" class="auth-acct" hidden>
687      <h3 style="font-family:var(--font-display);font-weight:600;font-size:1.5rem;margin-top:8px" data-i18n="auth.welcome">You're in.</h3>
688      <div class="auth-email" id="acctEmail"></div>
689      <a class="btn btn-primary" href="/app.html" data-i18n="auth.enter">Start practising →</a>
690      <button class="btn btn-ghost" id="subscribeBtn" data-i18n="auth.subscribe" style="margin-top:10px">Go premium</button>
691      <p class="sub-note" id="subNote" data-i18n="auth.subNote">Unlock every voice, every story, and unlimited shadowing.</p>
692      <button class="btn btn-ghost" id="signOutBtn" style="margin-top:14px" data-i18n="auth.signout">Sign out</button>
693    </div>
694  </div>
695</div>
696
697<script>
698document.documentElement.classList.add('js');
699
700/* Heal stale ShadowPlay PWA service worker / caches from older lomia.ai builds */
701if ('serviceWorker' in navigator) {
702  navigator.serviceWorker.getRegistrations().then((rs) => rs.forEach((r) => r.unregister())).catch(() => {});
703}
704if (self.caches) {
705  caches.keys().then((ks) => ks.forEach((k) => caches.delete(k))).catch(() => {});
706}
707
708/* ---------- i18n ---------- */
709const I18N = {
710  en: {
711    "meta.title":"Lomia — Learn a language by living inside stories",
712    "meta.desc":"Lomia turns anything you read into spoken story episodes — then listens as you say each line back, and brings the words you stumble on right back to you.",
713    "nav.brandAria":"Lomia home","nav.how":"How it works","nav.why":"Why it works","nav.features":"Features","nav.faq":"FAQ","nav.signin":"Sign in","nav.start":"Start free",
714    "hero.eyebrow":"Language, out loud",
715    "hero.h1":'<span class="l1">Don\'t just read</span><span class="l1">a language.</span><span class="l2"><em class="acc">Live it out loud.</em></span>',
716    "hero.lead":"Lomia turns anything you read into spoken story episodes — then listens as you say each line back, and brings the words you stumble on right back to you.",
717    "hero.cta1":"Start with a story","hero.cta2":'See how it works <span class="arr">↓</span>',
718    "hero.note":"No account needed to try · Works with any text you bring",
719    "device.scene":"Aesop · Tortoise &amp; Hare · 3","device.you":"You · say it back","device.recAria":"Record",
720    "metric.accuracy":"Accuracy","metric.fluency":"Fluency","metric.completeness":"Completeness","metric.prosody":"Prosody",
721    "marquee.cap":"Bring anything you actually read",
722    "marquee.row":'<span class="lbl">An <em>article</em></span><span class="sep"></span><span class="lbl">A novel <em>chapter</em></span><span class="sep"></span><span class="lbl">A podcast <em>transcript</em></span><span class="sep"></span><span class="lbl">An Aesop <em>fable</em></span><span class="sep"></span><span class="lbl">The <em>news</em></span><span class="sep"></span><span class="lbl">Song <em>lyrics</em></span><span class="sep"></span><span class="lbl">A work <em>email</em></span><span class="sep"></span>',
723    "who.eyebrow":"Sound familiar?",
724    "who.h2":'You can read it.<br>You just can\'t <em class="acc">say</em> it.',
725    "who.p":"You've passed the exams. You understand the films. But the moment you need to speak, the words don't come. Lomia is built for exactly that gap — between knowing a language and living inside it.",
726    "how.eyebrow":"How it works","how.h2":"Three steps from<br>a page to your voice.","how.p":"No decks of flashcards. No grammar drills. Just the loop that actually builds spoken language — listen, say it back, and hear what to fix.",
727    "how.s1t":"Paste anything","how.s1p":"An article, a chapter, a transcript, or a fable. The language you actually want to speak — not a textbook's idea of it.",
728    "how.s2t":"It becomes a story","how.s2p":"Lomia rewrites it into short, two-voice scenes pitched at your level — natural lines worth saying out loud.",
729    "how.s3t":"Shadow it","how.s3p":"Speak each line over a native voice. Hear yourself instantly, and see exactly which words and rhythms to chase.",
730    "diag.eyebrow":"Your voice, measured","diag.h2":"Not a grade. A diagnosis.","diag.p":"Every take is broken into the things that actually make you understood — so a low score always comes with a reason, and a way to fix it.",
731    "diag.c1t":"Words &amp; sounds","diag.c1p":"Edit-distance alignment matches what you said to the line — so one extra word never red-marks the rest. The sounds Mandarin speakers find hardest get singled out, gently.","diag.c1tag":"Accuracy · Completeness",
732    "diag.c2t":"Rhythm &amp; melody","diag.c2p":"Lomia scores your pace, pauses and pitch against the original. Because being understood is mostly prosody — the music of a sentence — not perfect phonemes.","diag.c2tag":"Fluency · Prosody",
733    "diag.c3t":"The words come back","diag.c3p":"Every line you stumble on becomes a memory the app tracks — and resurfaces right before you'd forget it, on a schedule tuned to your own curve.","diag.c3tag":"Spaced review",
734    "ev.eyebrow":"Built on the evidence","ev.h2":"Designed around how<br>language actually sticks.","ev.p":"Every choice in Lomia traces to 
734second-language research — comprehensible input, spaced retrieval, and the things shadowing is proven to train.",
735    "f1t":'Any text becomes <em class="acc">a scene.</em>',"f1p":"Paste a paragraph and watch it turn into a short conversation between two voices — rewritten to your level, never above it. The sweet spot, just past what you already know, where new language takes hold.",
736    "f1viz1":'“The annual report showed a marked decline in regional output, attributable to…”<span class="arrow">↓</span>',"f1viz2":"Role A · curious",
737    "f2t":'The words <em class="acc">come back.</em>',"f2p":"No streaks to protect, no guilt for a missed day. Lomia simply remembers the lines and words you struggled with, and quietly puts them in front of you again at exactly the right moment.",
738    "due.now":"due now","due.d2":"in 2d","due.d9":"in 9d","due.d21":"in 21d",
739    "f3t":'Hear your <em class="acc">past self.</em>',"f3p":"Lomia keeps your takes. A month in, set today's voice beside the one from week one on the same line — the kind of progress you can actually hear, not just a number going up.",
740    "cmp.then":"4 weeks ago","cmp.now":"today",
741    "cm.eyebrow":"Why not just…","cm.h2":'Three ways to study.<br>Only one makes you <em class="acc">speak.</em>',
742    "cm.r1":"You do","cm.r2":"Measured by","cm.r3":"You walk away","cm.badge":"Lomia",
743    "cm.aT":"Flashcard apps","cm.a1":"Memorize words in isolation","cm.a2":"Tapping that you recognize it","cm.a3":"Able to recognize — not produce",
744    "cm.bT":"Streak apps","cm.b1":"Protect a daily streak","cm.b2":"Logging in, day after day","cm.b3":"A habit of tapping — and guilt",
745    "cm.cT":"Lomia","cm.c1":"Speak real sentences out loud","cm.c2":"Four dimensions of being understood","cm.c3":"Actually able to speak — nothing to break",
746    "quote.text":'A language you can\'t speak isn\'t yours yet.<br>It\'s time to make it <em>yours</em> — out loud.',"quote.attr":"— The idea behind Lomia",
747    "st.eyebrow":"For the quietly fluent","st.h2":"You've studied for years.<br>Time to be heard.",
748    "st.l1":"dimensions scored on every line, not one opaque grade","st.l2":"sources — any text you bring becomes practice","st.l3":"streaks, hearts, or guilt — progress, not pressure","st.l4":"your voice against a native line, every single time",
749    "faq.eyebrow":"Questions","faq.h2":"Good to know.",
750    "faq.q1":"What can I put into Lomia?","faq.a1":"Anything you read in your target language — an article, a book chapter, a podcast transcript, song lyrics, even a work email. Lomia turns it into short spoken scenes at your level.",
751    "faq.q2":"Do I need a microphone?","faq.a2":"Yes — speaking is the whole point. Lomia listens to each line you say and shows you exactly what to improve, then brings the hard ones back later.",
752    "faq.q3":"Is this like Duolingo?","faq.a3":"No. There are no streaks to lose and no guilt for a missed day. Lomia measures whether you can be understood, and resurfaces the words you struggle with on a schedule tuned to your own memory.",
753    "faq.q4":"Which languages?","faq.a4":"Lomia starts with English for Mandarin speakers — the gap we know best — with more language pairs on the way.",
754    "faq.q5":"How fast can I start?","faq.a5":"Under a minute. Paste a page, pick a voice, and you're speaking your first line.",
755    "cta.h2":"Open a book.<br><em>Start a conversation.</em>","cta.lead":"Bring a page you've always meant to understand. In a minute, you'll be speaking it.","cta.b1":"Start with a story","cta.b2":'How it works <span class="arr">↓</span>',
756    "footer.tg":"For people who learned a language for years — and still don't speak it.",
757    "footer.c1":"Product","footer.science":"The science","footer.c2":"Stories","footer.s1":"Aesop's fables","footer.s2":"Bring your own","footer.s3":"News &amp; articles","footer.s4":"Transcripts","footer.c3":"Company","footer.co1":"About","footer.co2":"Privacy","footer.co3":"Contact",
758    "footer.base1":"© 2026 Lomia. Speak the story.","footer.base2":"Made for the quietly fluent.",
759    "auth.tabSignup":"Create account","auth.tabSignin":"Sign in",
760    "auth.emailLabel":"Email","auth.passLabel":"Password",
761    "auth.submitSignup":"Create account","auth.submitSignin":"Sign in",
762    "auth.foot":"No spam — just your spoken-language practice.",
763    "auth.welcome":"You're in.","auth.enter":"Start practising →","auth.subscribe":"Go premium","auth.signout":"Sign out",
764    "auth.subNote":"Unlock every voice, every story, and unlimited shadowing.",
765    "auth.notConfigured":"Sign-in isn't switched on yet — check back soon.",
766    "auth.checkEmail":"Check your inbox to confirm your email, then sign in.",
767    "auth.signedIn":"Signed in.","auth.working":"One moment…","auth.payOff":"Payments aren't enabled yet."
768  },
769  zh: {
770    "meta.title":"Lomia —— 走进故事里,学会一门语言",
771    "meta.desc":"Lomia 把你读到的任何文字,变成可以开口跟读的故事场景——再听你一句句说回来,并把你卡壳的词,在你快要忘记时重新带回你面前。",
772    "nav.brandAria":"Lomia 首页","nav.how":"如何运作","nav.why":"为何有效","nav.features":"功能","nav.faq":"常见问题","nav.signin":"登录","nav.start":"免费开始",
773    "hero.eyebrow":"让语言,开口说出来",
774    "hero.h1":'<span class="l1">
774别只是读懂</span><span class="l1">一门语言。</span><span class="l2"><em class="acc">把它,大声说出来。</em></span>',
775    "hero.lead":"Lomia 把你读到的任何文字,变成可以开口跟读的故事场景——再听你一句句说回来,并把你卡壳的词,在你快要忘记时重新带回你面前。",
776    "hero.cta1":"从一个故事开始","hero.cta2":'看看它怎么运作 <span class="arr">↓</span>',
777    "hero.note":"无需注册即可体验 · 任何文本都能变成练习",
778    "device.scene":"伊索 · 龟兔赛跑 · 3","device.you":"你 · 跟着说","device.recAria":"录音",
779    "metric.accuracy":"准确度","metric.fluency":"流利度","metric.completeness":"完整度","metric.prosody":"韵律",
780    "marquee.cap":"带来任何你真正会读的东西",
781    "marquee.row":'<span class="lbl">一篇<em>文章</em></span><span class="sep"></span><span class="lbl">一个<em>章节</em></span><span class="sep"></span><span class="lbl">一段<em>播客文字稿</em></span><span class="sep"></span><span class="lbl">一则<em>伊索寓言</em></span><span class="sep"></span><span class="lbl">今天的<em>新闻</em></span><span class="sep"></span><span class="lbl">一段<em>歌词</em></span><span class="sep"></span><span class="lbl">一封<em>工作邮件</em></span><span class="sep"></span>',
782    "who.eyebrow":"是不是很熟悉?",
783    "who.h2":'你能读懂,<br>却始终<em class="acc">说不出口</em>。',
784    "who.p":"考试你过了,电影你看懂了。可真要开口的那一刻,话却卡在嘴边出不来。Lomia 正是为这道鸿沟而生——在“看得懂”和“说得出”之间。",
785    "how.eyebrow":"如何运作","how.h2":"从一页文字,到你的声音,<br>只要三步。","how.p":"没有成沓的单词卡,没有语法操练。只有真正能练出口语的循环——听,说回来,听见自己哪里要改。",
786    "how.s1t":"粘贴任何文字","how.s1p":"一篇文章、一个章节、一段访谈,或一则寓言。你真正想说出口的语言,而不是教科书里的样子。",
787    "how.s2t":"它变成一个故事","how.s2p":"Lomia 把它改写成两个角色的简短场景,贴着你的水平——值得大声说出来的自然句子。",
788    "how.s3t":"跟着说","how.s3p":"在母语者的声音之上跟读每一句。即时听见自己,看清该追哪个词、哪段节奏。",
789    "diag.eyebrow":"你的声音,被量化","diag.h2":"不是一个分数,而是一份诊断。","diag.p":"每一次开口都会被拆解成真正让你“被听懂”的要素——所以低分总有原因,也总有改法。",
790    "diag.c1t":"词与音","diag.c1p":"编辑距离对齐会把你说的和原句逐词配准——多说一个词,后面的也不会再被连累标红。中国学习者最难的那些音,会被温柔地单独点出来。","diag.c1tag":"准确度 · 完整度",
791    "diag.c2t":"节奏与韵律","diag.c2p":"Lomia 把你的语速、停顿和音高与原声相比。因为“被听懂”靠的多是韵律——一句话的旋律,而非完美的音素。","diag.c2tag":"流利度 · 韵律",
792    "diag.c3t":"词会回来","diag.c3p":"每一个你卡住的句子,都会被记下,并在你快要忘记之前、按你自己的遗忘曲线重新出现。","diag.c3tag":"间隔复习",
793    "ev.eyebrow":"建立在证据之上","ev.h2":"围绕语言<br>
793真正被记住的方式来设计。","ev.p":"Lomia 的每一个选择都能追溯到二语习得研究——可理解输入、间隔提取,以及跟读被证明能练成的东西。",
794    "f1t":'任何文字,<em class="acc">都成一幕。</em>',"f1p":"粘进一段话,看它变成两个声音之间的简短对话——改写到你的水平,绝不超纲。就在你已知的边缘再进一步,语言最容易在那里扎根。",
795    "f1viz1":'“年度报告显示该地区产出明显下滑,原因可归结为……”<span class="arrow">↓</span>',"f1viz2":"角色 A · 好奇",
796    "f2t":'那些词,<em class="acc">会回来。</em>',"f2p":"没有要守护的连胜,没有缺席的愧疚。Lomia 只是记住你挣扎过的句子和词,在恰好的时刻,悄悄重新放到你眼前。",
797    "due.now":"现在到期","due.d2":"2 天后","due.d9":"9 天后","due.d21":"21 天后",
798    "f3t":'听见<em class="acc">过去的自己。</em>',"f3p":"Lomia 保留你的每一次录音。一个月后,把今天的声音和第一周的同一句话并排——这种进步,你听得见,而不只是一个往上走的数字。",
799    "cmp.then":"四周前","cmp.now":"今天",
800    "cm.eyebrow":"为什么不直接用……","cm.h2":'三种学习方式。<br>只有一种,让你<em class="acc">开口。</em>',
801    "cm.r1":"你做什么","cm.r2":"怎么衡量","cm.r3":"你最终得到","cm.badge":"Lomia",
802    "cm.aT":"单词卡 App","cm.a1":"孤立地背单词","cm.a2":"翻卡片、认得出","cm.a3":"认得出——却说不出",
803    "cm.bT":"打卡 App","cm.b1":"守住每日连胜","cm.b2":"日复一日地登录","cm.b3":"打卡的习惯——和愧疚",
804    "cm.cT":"Lomia","cm.c1":"大声说出真实的句子","cm.c2":"四个“被听懂”的维度","cm.c3":"真正能开口——没什么可断的",
805    "quote.text":'一门你说不出口的语言,还不算是你的。<br>是时候让它<em>属于你</em>了——大声地。',"quote.attr":"—— Lomia 的初心",
806    "st.eyebrow":"写给“沉默的熟练者”","st.h2":"你已经学了很多年。<br>是时候,被听见了。",
807    "st.l1":"每句话被评的维度,而非一个含糊的分数","st.l2":"来源——你带来的任何文字都是练习","st.l3":"没有连胜、体力、愧疚——是进步,不是压力","st.l4":"你的声音对一句母语原声,每一次",
808    "faq.eyebrow":"常见问题","faq.h2":"先了解一下。",
809    "faq.q1":"我可以放什么进 Lomia?","faq.a1":"任何你在目标语里读到的东西——文章、书的章节、播客文字稿、歌词,甚至一封工作邮件。Lomia 会把它变成贴合你水平的简短口语场景。",
810    "faq.q2":"需要麦克风吗?","faq.a2":"需要——开口才是关键。Lomia 会听你说的每一句,清楚告诉你该改什么,再在之后把难的那些重新带回来。",
811    "faq.q3":"这和多邻国一样吗?","faq.a3":"不一样。没有会断的连胜,也没有缺席的愧疚。Lomia 衡量的是你能否被听懂,并按你自己的记忆曲线,把你挣扎的词重新带回来。",
812    "faq.q4":"支持哪些语言?","faq.a4":"Lomia 从“中文母语者学英语”起步——这是我们最了解的鸿沟——更多语言组合在路上。",
813    "faq.q5":"多快能开始?","faq.a5":"不到一分钟。粘贴一页文字,选一个声音,你就能说出第一句。",
814    "cta.h2":"打开一本书,<br><em>开启一段对话。</em>","cta.lead":"带来一页你一直想读懂的文字。一分钟内,你就会开口说它。","cta.b1":"从一个故事开始","cta.b2":'它如何运作 <span class="arr">↓</span>',
815    "footer.tg":"写给那些——学了一门语言很多年,却依然开不了口的人。",
816    "footer.c1":"产品","footer.science":"科学依据","footer.c2":"故事","footer.s1":"伊索寓言","footer.s2":"带来你自己的","footer.s3":"新闻与文章","footer.s4":"访谈文字稿","footer.c3":"公司","footer.co1":"关于","footer.co2":"隐私","footer.co3":"联系",
817    "footer.base1":"© 2026 Lomia. 把故事,说出来。","footer.base2":"写给沉默的熟练者。",
818    "auth.tabSignup":"注册","auth.tabSignin":"登录",
819    "auth.emailLabel":"邮箱","auth.passLabel":"密码",
820    "auth.submitSignup":"创建账户","auth.submitSignin":"登录",
821    "auth.foot":"不发垃圾邮件——只为你的口语练习。",
822    "auth.welcome":"你已登录。","auth.enter":"开始练习 →","auth.subscribe":"升级会员","auth.signout":"退出登录",
823    "auth.subNote":"解锁全部声音、全部故事与无限跟读。",
824    "auth.notConfigured":"登录功能尚未开启,敬请期待。",
825    "auth.checkEmail":"请查收邮箱确认邮件,确认后即可登录。",
826    "auth.signedIn":"已登录。","auth.working":"请稍候……","auth.payOff":"支付尚未开启。"
827  }
828};
829
830function applyLang(lang){
831  const dict = I18N[lang] || I18N.en;
832  document.documentElement.lang = lang;
833  document.querySelectorAll('[data-i18n]').forEach(el=>{
834    const v = dict[el.getAttribute('data-i18n')];
835    if (v != null) el.innerHTML = v;
836  });
837  document.querySelectorAll('[data-i18n-aria]').forEach(el=>{
838    const v = dict[el.getAttribute('data-i18n-aria')];
839    if (v != null) el.setAttribute('aria-label', v);
840  });
841  document.querySelectorAll('[data-i18n-badge]').forEach(el=>{
842    const v = dict[el.getAttribute('data-i18n-badge')];
843    if (v != null) el.setAttribute('data-badge', v);
844  });
845  const metaEl = document.querySelector('[data-i18n-meta]');
846  if (metaEl && dict['meta.desc']) metaEl.setAttribute('content', dict['meta.desc']);
847  if (dict['meta.title']) document.title = dict['meta.title'];
848  // marquee (duplicated for seamless loop)
849  const row = document.getElementById('marquee-row');
850  if (row && dict['marquee.row']) row.innerHTML = dict['marquee.row'] + dict['marquee.row'];
851  // toggle active state
852  document.querySelectorAll('.lang-toggle button').forEach(b=>b.classList.toggle('active', b.dataset.lang===lang));
853  try { localStorage.setItem('fable.lang', lang); } catch(e){}
854}
855
856const saved = (()=>{ try { return localStorage.getItem('fable.lang'); } catch(e){ return null; } })();
857const initial = saved || ((navigator.language||'en').toLowerCase().startsWith('zh') ? 'zh' : 'en');
858applyLang(initial);
859document.querySelectorAll('.lang-toggle button').forEach(b=>b.addEventListener('click', ()=>applyLang(b.dataset.lang)));
860
861/* ---------- nav ---------- */
862const nav = document.getElementById('nav');
863const onScroll = ()=>nav.classList.toggle('scrolled', window.scrollY>24);
864onScroll(); window.addEventListener('scroll', onScroll, {passive:true});
865
866/* ---------- reveal + count-up ---------- */
867const reduce = window.matchMedia('(prefers-reduced-motion: reduce)').matches;
868function countUp(el){
869  const target = +el.dataset.count;
870  if (reduce){ el.textContent = target; return; }
871  const dur = 1100; let start = null;
872  function step(ts){ if(start===null) start=ts; const p=Math.min(1,(ts-start)/dur);
873    el.textContent = Math.round(p*target); if(p<1) requestAnimationFrame(step); }
874  requestAnimationFrame(step);
875}
876if (!reduce && 'IntersectionObserver' in window){
877  const io = new IntersectionObserver((entries)=>{
878    entries.forEach(e=>{ if(e.isIntersecting){
879      e.target.classList.add('in');
880      e.target.querySelectorAll && e.target.querySelectorAll('[data-count]').forEach(countUp);
881      io.unobserve(e.target);
882    }});
883  }, {threshold:0.16, rootMargin:'0px 0px -8% 0px'});
884  document.querySelectorAll('.reveal').forEach(el=>io.observe(el));
885} else {
886  document.querySelectorAll('.reveal').forEach(el=>el.classList.add('in'));
887  document.querySelectorAll('[data-count]').forEach(el=>el.textContent=el.dataset.count);
888}
889
890/* ---------- FAQ accordion ---------- */
891document.querySelectorAll('.faq-item').forEach(item=>{
892  const q = item.querySelector('.faq-q');
893  q.addEventListener('click', ()=>{
894    const open = item.classList.toggle('open');
895    q.setAttribute('aria-expanded', open ? 'true' : 'false');
896  });
897});
898
899/* ---------- device tilt ---------- */
900const device = document.getElementById('device');
901if (device && !reduce && window.matchMedia('(pointer:fine)').matches){
902  const hero = device.closest('.hero');
903  hero.addEventListener('mousemove', (ev)=>{
904    const r = hero.getBoundingClientRect();
905    const x = (ev.clientX-r.left)/r.width-0.5, y=(ev.clientY-r.top)/r.height-0.5;
906    device.style.transform = `perspective(1000px) rotateY(${x*5}deg) rotateX(${-y*5}deg)`;
907  });
908  hero.addEventListener('mouseleave', ()=>{ device.style.transform=''; });
909}
910
911/* ---------- scroll progress ---------- */
912const sprog = document.getElementById('sprog');
913function progress(){
914  const h = document.documentElement;
915  const max = h.scrollHeight - h.clientHeight;
916  sprog.style.width = (max>0 ? (h.scrollTop/max*100) : 0) + '%';
917}
918window.addEventListener('scroll', progress, {passive:true}); progress();
919
920/* ---------- hero background parallax ---------- */
921const heroBg = document.querySelector('.hero-bg');
922if (heroBg && !reduce){
923  window.addEventListener('scroll', ()=>{ heroBg.style.transform = `translateY(${window.scrollY*0.22}px)`; }, {passive:true});
924}
925
926/* ---------- magnetic primary buttons ---------- */
927if (!reduce && window.matchMedia('(pointer:fine)').matches){
928  document.querySelectorAll('.btn-primary').forEach(btn=>{
929    btn.addEventListener('pointermove', e=>{
930      const r = btn.getBoundingClientRect();
931      const x = e.clientX - r.left - r.width/2, y = e.clientY - r.top - r.height/2;
932      btn.style.transform = `translate(${x*0.18}px, ${y*0.3}px)`;
933    });
934    btn.addEventListener('pointerleave', ()=>{ btn.style.transform=''; });
935  });
936}
937
938/* ---------- card cursor spotlight ---------- */
939if (!reduce && window.matchMedia('(pointer:fine)').matches){
940  document.querySelectorAll('.card').forEach(c=>{
941    c.addEventListener('pointermove', e=>{
942      const r = c.getBoundingClientRect();
943      c.style.setProperty('--mx', (e.clientX-r.left)+'px');
944      c.style.setProperty('--my', (e.clientY-r.top)+'px');
945    });
946  });
947}
948
949/* ---------- Auth (Supabase) + checkout (Dodo) ---------- */
950(function(){
951  const overlay = document.getElementById('authOverlay');
952  const formView = document.getElementById('authForm');
953  const acctView = document.getElementById('authAccount');
954  const msg = document.getElementById('authMsg');
955  const submitBtn = document.getElementById('authSubmit');
956  const tabs = document.querySelectorAll('.auth-tabs button');
957  const emailIn = document.getElementById('authEmail');
958  const passIn = document.getElementById('authPass');
959  let mode = 'signup', sb = null, cfg = null, started = false;
960
961  const lang = () => document.documentElement.lang || 'en';
962  const t = (k) => (I18N[lang()] && I18N[lang()][k]) || I18N.en[k] || k;
963  const setMsg = (text, cls) => { msg.textContent = text || ''; msg.className = 'auth-msg' + (cls ? ' ' + cls : ''); };
964
965  function updateAuthMode(){
966    tabs.forEach(b => b.classList.toggle('active', b.dataset.mode === mode));
967    submitBtn.textContent = mode === 'signup' ? t('auth.submitSignup') : t('auth.submitSignin');
968    passIn.setAttribute('autocomplete', mode === 'signup' ? 'new-password' : 'current-password');
969  }
970
971  async function ensureClient(){
972    if (started) return sb;
973    started = true;
974    try { cfg = await (await fetch('/api/config')).json(); } catch (e) { cfg = {}; }
975    if (cfg && cfg.supabaseUrl && cfg.supabaseAnonKey){
976      try {
977        const mod = await import('https://esm.sh/@supabase/supabase-js@2');
978        sb = mod.createClient(cfg.supabaseUrl, cfg.supabaseAnonKey, { auth: { persistSession: true, autoRefreshToken: true } });
979        const { data } = await sb.auth.getSession();
980        if (data && data.session) showAccount(data.session.user);
981      } catch (e) { sb = null; }
982    }
983    return sb;
984  }
985
986  function showForm(){ acctView.hidden = true; formView.hidden = false; }
987  function showAccount(user){
988    formView.hidden = true; acctView.hidden = false;
989    document.getElementById('acctEmail').textContent = user.email || '';
990    if (cfg && !cfg.payments){
991      const sbtn = document.getElementById('subscribeBtn');
992      sbtn.disabled = true; sbtn.style.opacity = '.5';
993      document.getElementById('subNote').textContent = t('auth.payOff');
994    }
995  }
996
997  async function openAuth(m){
998    mode = m || 'signup';
999    overlay.hidden = false;
1000    requestAnimationFrame(() => overlay.classList.add('open'));
1001    setMsg(''); updateAuthMode();
1002    await ensureClient();
1003    if (!sb){ showForm(); setMsg(t('auth.notConfigured'), 'error'); submitBtn.disabled = true; }
1004    else { submitBtn.disabled = false; }
1005    setTimeout(() => emailIn && emailIn.focus(), 80);
1006  }
1007  function closeAuth(){ overlay.classList.remove('open'); setTimeout(() => { overlay.hidden = true; }, 300); }
1008
1009  document.querySelectorAll('[data-i18n="nav.signin"]').forEach(el => el.addEventListener('click', e => { e.preventDefault(); openAuth('signin'); }));
1010  document.querySelectorAll('[data-i18n="nav.start"],[data-i18n="hero.cta1"],[data-i18n="cta.b1"]').forEach(el => el.addEventListener('click', e => { e.preventDefault(); openAuth('signup'); }));
1011  tabs.forEach(b => b.addEventListener('click', () => { mode = b.dataset.mode; updateAuthMode(); setMsg(''); }));
1012  document.getElementById('authClose').addEventListener('click', closeAuth);
1013  overlay.addEventListener('click', e => { if (e.target === overlay) closeAuth(); });
1014  document.addEventListener('keydown', e => { if (e.key === 'Escape' && !overlay.hidden) closeAuth(); });
1015  document.querySelectorAll('.lang-toggle button').forEach(b => b.addEventListener('click', () => setTimeout(updateAuthMode, 0)));
1016
1017  document.getElementById('authFormEl').addEventListener('submit', async (e) => {
1018    e.preventDefault();
1019    if (!sb){ setMsg(t('auth.notConfigured'), 'error'); return; }
1020    const email = emailIn.value.trim(), password = passIn.value;
1021    if (!email || !password) return;
1022    submitBtn.disabled = true; setMsg(t('auth.working'));
1023    try {
1024      if (mode === 'signup'){
1025        const { data, error } = await sb.auth.signUp({ email, password });
1026        if (error) throw error;
1027        if (data.session) { showAccount(data.session.user); setMsg(''); }
1028        else setMsg(t('auth.checkEmail'), 'ok');
1029      } else {
1030        const { data, error } = await sb.auth.signInWithPassword({ email, password });
1031        if (error) throw error;
1032        showAccount(data.user); setMsg('');
1033      }
1034    } catch (err) { setMsg(err.message || 'Error', 'error'); }
1035    finally { submitBtn.disabled = false; }
1036  });
1037
1038  document.getElementById('subscribeBtn').addEventListener('click', async () => {
1039    if (!cfg || !cfg.payments){ document.getElementById('subNote').textContent = t('auth.payOff'); return; }
1040    const email = document.getElementById('acctEmail').textContent;
1041    const btn = document.getElementById('subscribeBtn'); btn.disabled = true;
1042    try {
1043      const r = await fetch('/api/checkout', { method: 'POST', headers: { 'content-type': 'application/json' }, body: JSON.stringify({ email }) });
1044      const j = await r.json();
1045      if (j.checkout_url) location.href = j.checkout_url;
1046      else { document.getElementById('subNote').textContent = j.error || t('auth.payOff'); btn.disabled = false; }
1047    } catch (e) { btn.disabled = false; }
1048  });
1049
1050  document.getElementById('signOutBtn').addEventListener('click', async () => {
1051    if (sb) { try { await sb.auth.signOut(); } catch (e) {} }
1052    showForm(); setMsg('');
1053  });
1054})();
1055</script>
vendor: 1 bytes, line 1055
1055
1056<script src="/yan.js"></script>
vendor: 1 bytes, line 1056
1056
1057<script>(function(){ if(window.Yan){ var h=document.getElementById('yanHero'); if(h) Yan.mount(h,{size:62}); } })();</script>
1057
1058</body>
1059</html>

Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.