PageSourceSearch

https://picturethewords.com/

html picturethewords.com collected 2026-10-02 01:39:06 UTC 30,873 bytes, 633 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.0">
6  <title>Picture the Words — Visual Vocabulary</title>
7  <meta name="description" content="Learn Japanese, Hindi and French vocabulary through illustrations, translated into English. Organised by topic.">
8
9  <!-- Favicons -->
10  <link rel="icon" type="image/x-icon" href="assets/icons/favicon.ico">
11  <link rel="icon" type="image/png" sizes="16x16" href="assets/icons/favicon-16.png">
12  <link rel="icon" type="image/png" sizes="32x32" href="assets/icons/favicon-32.png">
13  <link rel="icon" type="image/png" sizes="192x192" href="assets/icons/favicon-192.png">
14  <link rel="apple-touch-icon" sizes="180x180" href="assets/icons/favicon-180.png">
15
16  <!-- Fonts -->
17  <link rel="preconnect" href="https://fonts.googleapis.com">
18  <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
19  <link href="https://fonts.googleapis.com/css2?family=Fraunces:ital,opsz,wght,SOFT,WONK@0,9..144,400..800,0..100,0..1;1,9..144,400..800,0..100,0..1&family=Inter:opsz,[email protected],400..800&family=Noto+Sans+JP:wght@400;600;700&family=Noto+Serif+JP:wght@600;700&family=Noto+Sans+Devanagari:wght@400;600;700&display=swap" rel="stylesheet">
20
21  <!-- SEO -->
22  <link rel="canonical" href="https://picturethewords.com/">
23  <meta property="og:type" content="website">
24  <meta property="og:site_name" content="Picture the Words">
25  <meta property="og:url" content="https://picturethewords.com/">
26  <meta property="og:title" content="Picture the Words — Visual Vocabulary">
27  <meta property="og:description" content="Learn vocabulary through illustrations in Japanese, English, Hindi and French. 5,000 concepts, free to browse.">
28  <meta property="og:image" content="https://picturethewords.com/assets/icons/og-image.jpg">
29  <meta name="twitter:card" content="summary_large_image">
30  <meta name="twitter:title" content="Picture the Words — Visual Vocabulary">
31  <meta name="twitter:description" content="Learn vocabulary through illustrations in Japanese, English, Hindi and French. 5,000 concepts, free to browse.">
32  <meta name="twitter:image" content="https://picturethewords.com/assets/icons/og-image.jpg">
33  <meta name="theme-color" content="#0C3846">
34  <link rel="manifest" href="site.webmanifest">
35  
35<script type="application/ld+json">
36  {
37    "@context": "https://schema.org",
38    "@type": "WebSite",
39    "name": "Picture the Words",
40    "url": "https://picturethewords.com/",
41    "description": "Learn vocabulary through illustrations in Japanese, English, Hindi and French.",
42    "inLanguage": ["en", "ja", "hi", "fr"],
43    "potentialAction": {
44      "@type": "SearchAction",
45      "target": { "@type": "EntryPoint", "urlTemplate": "https://picturethewords.com/?q={search_term_string}" },
46      "query-input": "required name=search_term_string"
47    }
48  }
49  </script>
49
50
51  <link rel="stylesheet" href="styles.css?v=19">
52  
52<script>
53    // Offline support. Registered on every page so the whole site,
54    // not just the app, survives losing connection.
55    if ('serviceWorker' in navigator) {
56      window.addEventListener('load', () => {
57        navigator.serviceWorker.register('sw.js').catch(() => {});
58      });
59    }
60  </script>
60
61
62
63  <!-- Supabase (CDN version - no build step / npm needed) -->
64  
64<script src="https://cdn.jsdelivr.net/npm/@supabase/supabase-js@2/dist/umd/supabase.min.js"></script>
64
65  
65<script>
66    // Global Supabase connection, used by auth.js and future SRS code.
67    const SUPABASE_URL = "https://pqdrtqnmfzgnrderwrzl.supabase.co";
68    const SUPABASE_PUBLISHABLE_KEY = "sb_publishable_yAeNx_8QM8zLa26fQw_wMw_jelTeQep";
69    const supabaseClient = supabase.createClient(SUPABASE_URL, SUPABASE_PUBLISHABLE_KEY);
70  </script>
70
71  <style>
72    /* Inline fallback so this can't break if styles.css is stale/uncached */
73    #auth-overlay { display: none; position: fixed; inset: 0; z-index: 560;
74      background: rgba(12,56,70,.55); align-items: center; justify-content: center; }
75    #auth-overlay.open { display: flex; }
76    #review-overlay { display: none; position: fixed; inset: 0; z-index: 560;
77      background: rgba(12,56,70,.55); align-items: center; justify-content: center; }
78    #review-overlay.open { display: flex; }
79    #review-box { position: relative; background: #fff; border-radius: 16px;
80      max-width: 420px; width: 90%; padding: 32px; text-align: center; }
81    #review-card-area img { max-width: 100%; border-radius: 10px; cursor: pointer; }
82    #review-controls button { padding: 10px 20px; margin: 0 6px; border-radius: 8px;
83      border: none; font-weight: 600; cursor: pointer; }
84    .review-btn-again { background: #f4d4d0; color: #8a2e20; }
85    .review-btn-good { background: #cfe8d8; color: #1a5c33; }
86    #review-card-area img.review-crop-front { clip-path: inset(0 0 14% 0); }
87  </style>
88</head>
89<body>
90
91<!-- ═══════════ QUICK LOOKUP OVERLAY (mobile share target / clipboard) ═══════════ -->
92<div id="quick-lookup-overlay" role="dialog" aria-modal="true"
93  onclick="if(event.target===this)closeQuickLookup()">
94  <div class="quick-lookup-box">
95    <button class="focus-close" onclick="closeQuickLookup()" aria-label="Close">✕</button>
96    <div class="quick-lookup-header">
97      <h3>Quick Lookup</h3>
98    </div>
99    <div id="quick-lookup-input-area">
100      <input type="text" id="quick-lookup-input" placeholder="Type or paste text…"
101        oninput="runQuickLookup(this.value)" autocomplete="off" spellcheck="false">
102    </div>
103    <div id="quick-lookup-results"></div>
104  </div>
105</div>
106
107<!-- ═══════════ SCAN OVERLAY (camera OCR -> Quick Lookup) ═══════════
108     Feature 4, narrowed 2026-09-20: the original "scan-anything object
109     detection" spec was found not achievable at this project's 100%-accuracy
110     bar (no image-classification model reaches 100% on open-set real-world
111     photos). This is a different, deterministic feature instead: OCR reads
112     TEXT from a photo, and the extracted text is looked up through the
113     exact same already-verified dictionary pipeline Quick Lookup uses --
114     so the translation/definition shown is never less accurate than typing
115     the word in by hand. The only uncertain step is what the OCR engine
116     read, and that is never hidden: the extracted text is shown, editable,
117     in the Quick Lookup input right after a scan, before any result is
118     asserted. -->
119<div id="scan-overlay" role="dialog" aria-modal="true"
120  onclick="if(event.target===this)closeScanOverlay()">
121  <div class="quick-lookup-box scan-box">
122    <button class="focus-close" onclick="closeScanOverlay()" aria-label="Close">✕</button>
123    <div class="quick-lookup-header">
124      <h3>Scan Text</h3>
125    </div>
126    <div id="scan-body">
127      <p class="scan-hint">Photograph a sign, menu, or label. The text found in the photo
128        will be looked up automatically -- nothing is guessed from the image itself.</p>
129      <label class="scan-capture-btn" id="scan-capture-label">
130        <input type="file" id="scan-file-input" accept="image/*" capture="environment"
131          onchange="handleScanFile(this.files[0])">
132        &#128247; Take a Photo
133      </label>
134      <div id="scan-status"></div>
135    </div>
136  </div>
137</div>
138
139<!-- ═══════════ FOCUS OVERLAY (concept detail, with direction tabs) ═══════════ -->
140<div id="focus-overlay" role="dialog" aria-modal="true"
141  onclick="if(event.target===this)closeFocus()">
142  <button class="focus-close" onclick="closeFocus()" aria-label="Close">✕</button>
143  <div id="focus-card-wrap">
144    <div id="focus-card-inner"></div>
145  </div>
146</div>
147
148<!-- ═══════════ GRAMMAR OVERLAY ═══════════
149     Separate from the topic-category system on purpose: grammar concepts
150     have no card image, just term + translations + audio, so this reuses
151     the overlay chrome (close button, click-outside-to-close) but not the
152     illustrated-card layout. -->
153<div id="grammar-overlay" role="dialog" aria-modal="true"
154  onclick="if(event.target===this)closeGrammar()">
155  <button class="focus-close" onclick="closeGrammar()" aria-label="Close">✕</button>
156  <div class="grammar-box" onclick="event.stopPropagation()">
157    <div id="grammar-list-view">
158      <h2 class="grammar-title">Grammar</h2>
159      <div id="grammar-groups"></div>
160    </div>
161    <div id="grammar-detail-view" style="display:none;">
162      <button class="grammar-back" onclick="showGrammarList()">← Back</button>
163      <div id="grammar-detail-body"></div>
164    </div>
165  </div>
166</div>
167
168<!-- ═══════════ USAGE OVERLAY ═══════════
169     "View usage" sub-page for a single inflected form, opened from within
170     an already-open flashcard's Inflected forms section -- so it needs a
171     higher z-index than #focus-overlay to actually appear on top of it
172     (see styles.css), not a separate top-level destination like grammar/
173     search below. -->
174<div id="usage-overlay" role="dialog" aria-modal="true"
175  onclick="if(event.target===this)closeUsage()">
176  <button class="focus-close" onclick="closeUsage()" aria-label="Close">✕</button>
177  <div class="usage-box" onclick="event.stopPropagation()">
178    <div id="usage-body"></div>
179  </div>
180</div>
181
182<!-- ═══════════ SEARCH OVERLAY ═══════════ -->
183<div id="search-overlay">
184  <div class="sov-box" onclick="event.stopPropagation()">
185    <div class="search-mode-toggle">
186      <button id="search-mode-words" class="search-mode-btn active" onclick="if(SEARCH_MODE!=='words')toggleSearchMode()">Words</button>
187      <button id="search-mode-sentences" class="search-mode-btn" onclick="if(SEARCH_MODE!=='sentences')toggleSearchMode()">Sentences</button>
188    </div>
189    <div class="sov-input-row">
190      <span class="sov-icon">🔍</span>
191      <input id="search-input" type="text" placeholder="Search words..."
192        oninput="runSearch(this.value)" autocomplete="off" spellcheck="false">
193      <span class="sov-esc" onclick="closeSearch()">Esc</span>
194    </div>
195    <div id="search-results"></div>
196  </div>
197</div>
198
199<!-- ═══════════ AUTH OVERLAY ═══════════ -->
200<div id="auth-overlay" onclick="if(event.target===this)closeAuth()">
201  <div class="info-box" onclick="event.stopPropagation()" style="max-width:340px;">
202    <button class="focus-close" onclick="closeAuth()" aria-label="Close">✕</button>
203    <div class="auth-tabs" style="display:flex; gap:8px; margin-bottom:14px;">
204      <button id="auth-tab-login" class="nav-info-link" onclick="setAuthTab('login')">Log In</button>
205      <button id="auth-tab-signup" class="nav-info-link" onclick="setAuthTab('signup')">Sign Up</button>
206    </div>
207    <div style="display:flex; flex-direction:column; gap:10px;">
208      <input id="auth-form-email" type="email" placeholder="Email" autocomplete="email">
209      <input id="auth-form-password" type="password" placeholder="Password" autocomplete="current-password">
210      <button id="auth-submit-btn" onclick="submitAuthForm()">Log In</button>
211      <div id="auth-message" style="font-size:13px; min-height:18px;"></div>
212    </div>
213  </div>
214</div>
215
216<!-- ═══════════ REVIEW OVERLAY ═══════════ -->
217<div id="review-overlay" onclick="if(event.target===this)closeReview()">
218  <div id="review-box" onclick="event.stopPropagation()">
219    <button class="focus-close" onclick="closeReview()" aria-label="Close">✕</button>
220    <div id="review-progress" style="font-size:13px; opacity:.7; margin-bottom:10px;"></div>
221    <div id="review-card-area"></div>
222    <div id="review-controls" style="margin-top:16px;"></div>
223  </div>
224</div>
225
226<!-- ═══════════ ADD TO CATEGORY OVERLAY ═══════════ -->
227<!-- Existing categories are real tappable buttons here -- the previous
228     version used a plain text prompt() that listed category names as
229     a sentence and made you type one out by hand to match it exactly.
230     Deliberately kept OUTSIDE #page-home: its trigger button lives inside
231     #focus-overlay, which opens from either page, but #page-home is
232     display:none whenever #page-app (category browsing) is active -- so
233     when this overlay was nested inside #page-home it silently rendered
234     at zero size any time "Add to category" was clicked while browsing a
235     category instead of sitting on the homepage. -->
236<div id="add-category-overlay" onclick="if(event.target===this)closeAddToCategoryPicker()">
237  <div class="info-box" onclick="event.stopPropagation()" style="max-width:420px;">
238    <button class="focus-close" onclick="closeAddToCategoryPicker()" aria-label="Close">✕</button>
239    <h3 style="margin-bottom:12px;">Add to category</h3>
240    <div id="add-category-existing"></div>
241    <div style="display:flex; gap:8px; margin-top:14px;">
242      <input type="text" id="add-category-new-name" placeholder="New category name" style="flex:1;">
243      <button class="btn-primary" onclick="handleAddToNewCategory()">Create &amp; add</button>
244    </div>
245  </div>
246</div>
247
248<!-- ═══════════ MY NOTES OVERLAY ═══════════ -->
249<!-- Deliberately kept OUTSIDE #page-home, same reasoning as
250     #add-category-overlay above: its trigger (the account dropdown) lives
251     in the topbar that's shared between the homepage and category-browsing
252     views, but #page-home is display:none whenever #page-app is active --
253     so nested inside #page-home this silently rendered at zero size any
254     time "My Notes" was opened from a category view instead of the
255     homepage. -->
256<div id="notes-overlay" onclick="if(event.target===this)closeMyNotes()">
257  <div class="info-box" onclick="event.stopPropagation()" style="max-width:520px;">
258    <button class="focus-close" onclick="closeMyNotes()" aria-label="Close">✕</button>
259    <h3 style="margin-bottom:14px;">My Notes</h3>
260    <div id="notes-list-content"></div>
261  </div>
262</div>
263
264<!-- ═══════════ MY ACCOUNT OVERLAY ═══════════ -->
265<!-- My account / My Categories / Favorites / History each open this
266     same modal directly, on their own dropdown click -- no shared tab
267     bar between them (previously all four were tabs of one "My Account"
268     overlay, so opening Favorites also surfaced My Categories and
269     History right there, which is not what a dedicated one-click
270     dropdown entry should do). Kept outside #page-home for the same
271     reason as #notes-overlay directly above. -->
272<div id="account-overlay" onclick="if(event.target===this)closeMyAccount()">
273  <div class="info-box" onclick="event.stopPropagation()" style="max-width:520px;">
274    <button class="focus-close" onclick="closeMyAccount()" aria-label="Close">✕</button>
275    <div id="account-tab-body"></div>
276  </div>
277</div>
278
279<!-- ═══════════ CREATE / EDIT CUSTOM CARD OVERLAY ═══════════ -->
280<div id="create-card-overlay" onclick="if(event.target===this)closeCreateCardForm()">
281  <div class="info-box" onclick="event.stopPropagation()" style="max-width:420px;">
282    <button class="focus-close" onclick="closeCreateCardForm()" aria-label="Close">✕</button>
283    <h3 id="create-card-title" style="margin-bottom:14px;">Create a card</h3>
284
285    <label class="scan-capture-btn" id="create-card-image-label" style="margin-bottom:12px;">
286      <input type="file" id="create-card-image-input" accept="image/*"
287        onchange="handleCreateCardImagePick(this.files[0])">
288      &#128247; Choose a Photo
289    </label>
290    <div id="create-card-image-preview" class="create-card-image-preview"></div>
291
292    <div class="create-card-field">
293      <label for="create-card-en">English</label>
294      <input type="text" id="create-card-en" placeholder="e.g. umbrella" autocomplete="off">
295    </div>
296    <div class="create-card-field">
297      <label for="create-card-ja">Japanese</label>
298      <input type="text" id="create-card-ja" placeholder="e.g. 傘" autocomplete="off">
299    </div>
300    <div class="create-card-field">
301      <label for="create-card-hi">Hindi</label>
302      <input type="text" id="create-card-hi" placeholder="e.g. छाता" autocomplete="off">
303    </div>
304    <div class="create-card-field">
305      <label for="create-card-fr">French</label>
306      <input type="text" id="create-card-fr" placeholder="e.g. parapluie" autocomplete="off">
307    </div>
308
309    <div id="create-card-status" class="create-card-status"></div>
310    <div style="display:flex; gap:8px; margin-top:10px;">
311      <button class="btn-primary" style="flex:1;" onclick="submitCustomCard()">Save</button>
312      <button class="card-action card-action-quiet" id="create-card-delete-btn"
313        style="display:none;" onclick="if(confirm('Delete this card?'))deleteCustomCard(ptwEditingCardId)">Delete</button>
314    </div>
315  </div>
316</div>
317
318<!-- ═══════════ HOME PAGE ═══════════ -->
319<div id="page-home" class="page">
320
321  <nav class="home-nav">
322    <div class="nav-brand" onclick="showPage('home')" role="link" tabindex="0">
323      <img class="nav-logo" src="assets/icons/ptw-logo.png" alt="Picture the Words logo">
324      <span class="nav-brand-name">Picture the Words</span>
325    </div>
326
327    <div class="nav-links">
328      <a class='nav-info-link' href='/how-to-use'>How it works</a>
329  <a class='nav-info-link' href='/situations'>Situations</a>
330      <a class='nav-info-link' href='/pricing'>Pricing</a>
331      <a class='nav-info-link' href='/included'>What's included</a>
332      <a class='nav-info-link' href='/about'>About</a>
333    </div>
334
335    <div class="nav-spacer"></div>
336
337    <div class="nav-right">
338      <div id="offline-indicator" class="offline-indicator" style="display:none;" title="You're offline - cached content available">
339        <span class="offline-indicator-icon">&#128268;</span>
340        <span class="offline-indicator-text">Offline</span>
341      </div>
342      <button class="nav-search-btn" onclick="openSearch()" aria-label="Search the dictionary">
343        <svg viewBox="0 0 20 20" fill="none" stroke="currentColor" stroke-width="2">
344          <circle cx="8.5" cy="8.5" r="5.5"/><line x1="13" y1="13" x2="18" y2="18"/>
345        </svg>
346        Search
347        <span class="nav-kbd" id="search-kbd">Ctrl K</span>
348      </button>
349      <div class="auth-nav-area" id="auth-nav-area"></div>
350    </div>
351  </nav>
352
353  <div class="hero">
354    <div class="hero-script-bg" aria-hidden="true">
355      <span>あ</span><span>A</span><span>か</span><span>B</span><span>さ</span><span>C</span>
356      <span>た</span><span>T</span><span>な</span><span>N</span><span>は</span><span>H</span>
357      <span>ま</span><span>M</span><span>や</span><span>Y</span><span>ら</span><span>R</span>
358      <span>わ</span><span>W</span><span>き</span><span>K</span><span>し</span><span>S</span>
359      <span>ち</span><span>D</span><span>に</span><span>P</span><span>ひ</span><span>F</span>
360    </div>
361    <div class="hero-left">
362      <div class="hero-eyebrow">
363        <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
364          <rect x="3" y="3" width="18" height="18" rx="2"/>
365          <circle cx="8.5" cy="8.5" r="1.5"/>
366          <path d="M21 15l-5-5L5 21"/>
367        </svg>
368        Visual Vocabulary
369      </div>
370      <h1 class="hero-title">Picture the <em>language.</em></h1>
371      <p class="hero-subhead">Build fluency one picture at a time.</p>
372      <p class="hero-body">Every illustration connects Japanese, English, Hindi and French in all directions — organised by topic, remembered through pictures rather than word lists.</p>
373
374      <div class="hero-cta-row">
375        <button class="btn-primary" onclick="startLearning()">Start learning — free</button>
376        <button class="btn-secondary" onclick="openQuickLookup()">Quick Lookup</button>
377      </div>
378
379      <p class="hero-mvp-line">
380        <a href='/reader'>Reader Mode</a> — paste Japanese text and tap any word to look it up.
381        <a href='/bookmarklet'>Mobile bookmarklet</a> for quick lookups on any site.
382      </p>
383
384      <p class="hero-mvp-line">
385        All 5,000 concepts are live, in four languages.
386        <a href='/included'>See exactly what's included</a>.
387      </p>
388    </div>
389    <div class="hero-right" id="hero-stats-panel">
390      <div class="deco-cards" id="deco-cards" aria-hidden="true">
391        <img class="deco-card deco-card-1" id="deco-card-1" alt="">
392        <img class="deco-card deco-card-2" id="deco-card-2" alt="">
393        <img class="deco-card deco-card-3" id="deco-card-3" alt="">
394      </div>
395    </div>
396  </div>
397
398  <!-- ═══════════ STAT BAR ═══════════ -->
399  <section class="stat-bar" aria-label="What's in the library">
400    <div class="stat-bar-inner">
401      <div class="stat-cell">
402        <span class="stat-cell-num" id="stat-total">5,000</span>
403        <span class="stat-cell-label">Illustrated concepts</span>
404      </div>
405      <div class="stat-cell">
406        <span class="stat-cell-num" id="stat-cards">2,000</span>
407        <span class="stat-cell-label">Flashcards</span>
408      </div>
409      <div class="stat-cell">
410        <span class="stat-cell-num">4</span>
411        <span class="stat-cell-label">Languages, three directions</span>
412      </div>
413      <div class="stat-cell">
414        <span class="stat-cell-num" id="stat-audio">2,000</span>
415        <span class="stat-cell-label">Audio recordings</span>
416      </div>
417      <div class="stat-cell">
418        <span class="stat-cell-num" id="stat-topics">15</span>
419        <span class="stat-cell-label">Topics</span>
420      </div>
421    </div>
422  </section>
423
424  <section id="usage-bar" class="usage-bar" style="display:none;" aria-label="Usage"></section>
425
426  <div class="home-cats">
427    <h2 class="home-cats-title">Browse by <em>topic</em></h2>
428    <p class="home-cats-sub">Fifteen everyday domains, each one illustrated in all three directions.</p>
429    <div id="home-cat-grid"></div>
430  </div>
431
432  <!-- Grammar is deliberately separate from the 15 topic categories above --
433       it's function words (pronouns, prepositions, question words...) that
434       don't illustrate the same way content words do, so it gets its own
435       entry point rather than being folded into the topic grid. -->
436  <div class="home-grammar" id="home-grammar-section" style="display:none;">
437    <h2 class="home-cats-title">Or study <em>grammar</em></h2>
438    <p class="home-cats-sub">Pronouns, question words, prepositions and more — the building blocks every language shares.</p>
439    <div class="grammar-entry-tile" onclick="openGrammar()"
440         onkeydown="if(event.key==='Enter'||event.key===' '){event.preventDefault();openGrammar()}"
441         role="button" tabindex="0" aria-label="Browse grammar concepts">
442      <div class="grammar-entry-icon">Aあ</div>
443      <div class="grammar-entry-text">
444        <div class="grammar-entry-name">Grammar</div>
445        <div class="grammar-entry-count" id="grammar-entry-count"></div>
446      </div>
447      <div class="grammar-entry-arrow">→</div>
448    </div>
449  </div>
450
451  <!-- Site updates live here rather than on a separate changelog page.
452       Add a new list item at the top with each release; keep three. -->
453  <section class="whatsnew" aria-label="Recent updates">
454    <div class="whatsnew-inner">
455      <h2 class="whatsnew-title">What's new</h2>
456      <ul class="whatsnew-list">
457        <li>
458          <span class="whatsnew-date">July 2026</span>
459          <span class="whatsnew-text">Redesigned site, a new <a href='/included'>What's included</a> page, and regional pricing shown in your own currency.</span>
460        </li>
461        <li>
462          <span class="whatsnew-date">July 2026</span>
463          <span class="whatsnew-text">Personal notes and custom categories now sync across the website and the browser extension.</span>
464        </li>
465        <li>
466          <span class="whatsnew-date">June 2026</span>
467          <span class="whatsnew-text">Spaced-repetition review added, with cards you save while browsing appearing straight in your deck.</span>
468        </li>
469      </ul>
470    </div>
471  </section>
472
473  <div class="home-vision">
474    <p class="home-vision-text">One visual system. Built to carry more languages.</p>
475  </div>
476
477  <section id="reviews-section" class="home-reviews"></section>
478
479  <footer class="ptw-footer">
480    <div class="ptw-footer-grid">
481      <div class="ptw-footer-brand">
482        <img src="assets/icons/ptw-logo.png" alt="Picture the Words">
483        <p class="ptw-footer-tagline">
484          A visual dictionary and flashcard system for languages, built on illustration
485          as well as translation.
486        </p>
487      </div>
488      <div class="ptw-footer-col">
489        <h4>Product</h4>
490        <ul>
491          <li><a href='/'>Visual dictionary</a></li>
492          <li><a href='/situations'>Situations</a></li>
493          <li><a href='/how-to-use'>Browser extension</a></li>
494          <li><a href='/pricing'>Pricing</a></li>
495          <li><a href='/included'>What's included</a></li>
496        </ul>
497      </div>
498      <div class="ptw-footer-col">
499        <h4>Learn</h4>
500        <ul>
501          <li><a href='/how-to-use'>How it works</a></li>
502          <li><a href='/included#roadmap'>Roadmap</a></li>
503          <li><a href='/about'>About the project</a></li>
504        </ul>
505      </div>
506      <div class="ptw-footer-col">
507        <h4>Company</h4>
508        <ul>
509          <li><a href='/contact'>Contact</a></li>
510          <li><a href='/terms'>Terms of Use</a></li>
511          <li><a href='/privacy'>Privacy Policy</a></li>
512        
513        <li><a href='/sources-and-licenses'>Sources &amp; Licenses</a></li>
514      </ul>
515      </div>
516    </div>
517    <div class="ptw-footer-base">
518    <p>
519      Flashcard content, structural design, and illustrations are original to Picture the Words. &copy; Picture the Words.
520    </p>
521    <p>
522      Dictionary and example-sentence data draw in part on third-party open resources, used under
523      their respective licences. See <a href='/sources-and-licenses'>Sources &amp; Licenses</a>
524      for full details and attribution.
525    </p>
526  </div>
527  </footer>
528
529</div>
530
531<!-- ═══════════ APP PAGE ═══════════ -->
532<div id="page-app" class="page">
533
534  <!-- Topbar -->
535  <div class="app-topbar">
536    <div class="topbar-home" onclick="showPage('home')">
537      <img class="ptw-footer-logo" src="assets/icons/ptw-logo.png" alt="Picture the Words logo">
538      <span style="font-size:12px;font-weight:600;color:var(--teal-dark)">Home</span>
539    </div>
540    <span class="topbar-sep">›</span>
541    <div class="topbar-crumb-wrap">
542      <span id="topbar-crumb">Loading…</span>
543    </div>
544
545    <!-- Category switcher for narrow screens -- .app-sidebar is hidden
546         below 640px (there's no room for a 15-item list next to the deck),
547         but that left phone visitors with literally no way to switch
548         categories at all once inside one, only a way back to the
549         homepage. Hidden on desktop by the same breakpoint that hides the
550         sidebar, so the two never show at once. -->
551    <select id="mobile-category-select" class="mobile-category-select"
552      onchange="if(this.value) openCategory(this.value)" aria-label="Switch category">
553      <option value="">Switch category…</option>
554    </select>
555
556    <!-- Study preferences live here, where they're actually used, rather
557         than occupying the homepage hero. -->
558    <div class="topbar-prefs">
559      <span class="topbar-prefs-label">Studying</span>
560      <select id="pref-direction" onchange="setPrefDirection(this.value)" aria-label="Study direction">
561        <option value="JA_EN">Japanese → English</option>
562        <option value="HI_EN">Hindi → English</option>
563        <option value="FR_EN">French → English</option>
564      </select>
565      <span id="pref-allowance-badge" class="yes-badge hidden" onclick="location.href='pricing.html'" style="cursor:pointer;"></span>
566      <span class="topbar-prefs-label">Labels</span>
567      <select id="pref-lang" onchange="setPrefLang(this.value)" aria-label="Word label language">
568        <option value="en">English</option>
569        <option value="ja">Japanese</option>
570        <option value="hi">Hindi</option>
571        <option value="fr">French</option>
572      </select>
573    </div>
574
575    <div class="topbar-actions">
576      <div id="offline-indicator-app" class="offline-indicator" style="display:none;" title="You're offline - cached content available">
577        <span class="offline-indicator-icon">&#128268;</span>
578        <span class="offline-indicator-text">Offline</span>
579      </div>
580      <div id="sync-indicator" class="sync-indicator" style="display:none;" title="Syncing changes...">
581        <span class="sync-indicator-icon">&#8635;</span>
582        <span class="sync-indicator-text">Syncing</span>
583      </div>
584      <button class="topbar-search-btn" onclick="openSearch()">
585        <svg width="13" height="13" viewBox="0 0 20 20" fill="none" stroke="currentColor" stroke-width="2">
586          <circle cx="8.5" cy="8.5" r="5.5"/><line x1="13" y1="13" x2="18" y2="18"/>
587        </svg>
588        Search
589      </button>
590      <button class="topbar-btn topbar-lookup-btn" id="lookup-btn" onclick="openQuickLookup()" title="Quick Lookup">&#128269; Lookup</button>
591      <button class="topbar-btn topbar-scan-btn" id="scan-btn" onclick="openScanOverlay()" title="Scan text with your camera">&#128247; Scan</button>
592      <button class="topbar-btn" id="stars-btn" onclick="toggleStarsOnly()">★ Starred</button>
593      <button class="topbar-btn" id="offline-btn" onclick="openOfflineAccess()" title="Manage offline downloads">&#128229; Offline</button>
594      <div class="auth-nav-area"></div>
595    </div>
596  </div>
597
598  <!-- Body -->
599  <div class="app-body">
600
601    <!-- Sidebar -->
602    <aside class="app-sidebar">
603      <div id="sidebar-nav"></div>
604    </aside>
605
606    <!-- Main -->
607    <main class="app-main">
608      <div id="cat-header" class="cat-header">
609        <div class="cat-header-info">
610          <div id="cat-header-label">Category</div>
611          <div id="cat-header-count">0 words</div>
612        </div>
613      </div>
614      <div id="card-deck" class="deck"></div>
615    </main>
616
617  </div>
618</div>
619
620<script src="auth.js?v=10"></script>
vendor: 1 bytes, line 620
620
621<script src="srs.js?v=9"></script>
vendor: 1 bytes, line 621
621
622<script src="entitlement.js?v=9"></script>
vendor: 1 bytes, line 622
622
623<script src="custom-cards.js?v=1"></script>
vendor: 1 bytes, line 623
623
624<script src="notes.js?v=9"></script>
vendor: 1 bytes, line 624
624
625<script src="reviews.js?v=8"></script>
vendor: 1 bytes, line 625
625
626<script src="account.js?v=11"></script>
vendor: 1 bytes, line 626
626
627<script src="offline-download.js?v=1"></script>
vendor: 1 bytes, line 627
627
628<script src="stats.js?v=8"></script>
vendor: 1 bytes, line 628
628
629<script src="gen-dict.js?v=1"></script>
vendor: 1 bytes, line 629
629
630<script src="mobile-lookup.js?v=2"></script>
vendor: 1 bytes, line 630
630
631<script src="script.js?v=28"></script>
631
632</body>
633</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.