CRITICAL: When developing for this project, you must adhere to the following constraints to ensure compatibility with Kindle and E-ink browsers.
Constraint: Chromium 75 supports gap for CSS Grid but NOT for Flexbox (added in Chrome 84).
Solution:
- Flex Containers: Use Margins (
margin-left/margin-topon siblings). - Grid Containers: You CAN use
gap. Prefer CSS Grid for layouts requiring gutters.
Example (Correct):
/* OK in Grid */
.grid-box { display: grid; gap: 10px; }
/* BROKEN in Flex (Do NOT use) */
.flex-box { display: flex; gap: 10px; }Constraint: The browser supports up to ES2019. BANNED Syntax (ES2020+):
- ❌ Optional Chaining (
?.) ->user?.namewill CRASH the app. - ❌ Nullish Coalescing (
??) ->val ?? defaultwill CRASH the app. - ✅
async/await,Promises,Arrow Functionsare SAFE.
Constraint: E-ink displays run at ~7-15fps. CSS animations cause severe ghosting and flashing. Solution: Disable all animations.
* {
transition: none !important;
animation: none !important;
}Constraint: window.alert(), confirm(), and prompt() are unsupported.
Solution: Use Custom Modals (HTML/CSS overlays).
Example (Correct):
<!-- Use a custom div overlay -->
<div id="custom-alert" class="modal-overlay">
<div class="modal-box">
<p>Operation failed.</p>
<button onclick="closeModal()">OK</button>
</div>
</div>All applications must adhere to the following strict HTML/CSS patterns to maintain the "Retro OS" look.
The body acts as the "desktop" background. It handles the centering of the application window.
body {
background-color: #e5e5e5; /* Desktop Gray */
font-family: "Geneva", "Verdana", sans-serif;
image-rendering: pixelated; /* CRITICAL for crisp edges */
margin: 0;
height: 100vh;
overflow: hidden; /* Prevent body scroll */
/* Center the App Window */
display: flex;
align-items: center;
justify-content: center;
}The main container for every app.
:root {
--shadow: 4px 4px 0px #000000;
}
.window {
background: white;
border: 2px solid black;
box-shadow: var(--shadow); /* Hard, non-blurred shadow */
width: 95%;
max-width: 600px; /* Standard Tablet Width */
height: 90vh; /* Or fit-content */
display: flex;
flex-direction: column;
position: relative;
}Mandatory Structure: The title bar uses a specific layered technique to achieve the "text on stripes" look.
HTML:
<div class="title-bar">
<div class="title-stripes"></div>
<div class="close-box" onclick="window.location.href='index'">X</div>
<span class="title-text" data-i18n="app.title">My App</span>
</div>CSS:
:root {
--stripe-pattern: repeating-linear-gradient(0deg, transparent, transparent 2px, #000 3px, #000 4px);
}
.title-bar {
height: 35px;
border-bottom: 2px solid black;
display: flex;
align-items: center;
justify-content: center;
background: white;
position: relative; /* Context for absolute children */
}
/* The Striped Background Layer */
.title-stripes {
position: absolute;
top: 4px; bottom: 4px; left: 4px; right: 4px;
background-image: var(--stripe-pattern);
z-index: 0;
}
/* The centered text with white background blocking stripes */
.title-text {
background: white;
padding: 0 15px;
font-weight: bold;
font-size: 1.1rem;
z-index: 1; /* Sits above stripes */
}
/* Standard Close Button */
.close-box {
position: absolute;
left: 10px;
width: 18px; height: 18px;
border: 2px solid black;
background: white;
z-index: 2; /* Sits above everything */
box-shadow: 2px 2px 0 black;
cursor: pointer;
/* Flex center content "X" */
display: flex; align-items: center; justify-content: center;
}Buttons and inputs share a "tactile" 2px border style.
- Buttons:
border: 2px solid black,box-shadow: 2px 2px 0 black.- Active State:
transform: translate(2px, 2px),box-shadow: none,background: black,color: white.
- Active State:
- Inputs:
border: 2px solid black,border-radius: 0,font-family: inherit.
Strict layering constants to prevent overlap issues.
| Component | Z-Index | Notes |
|---|---|---|
title-stripes |
0 |
Background pattern |
title-text |
1 |
Sits above stripes |
close-box |
2 |
Interactive top layer |
modal-overlay |
10000 |
Always top-most |
Standardized "Beta" or status badges.
Beta Badge:
.beta-badge {
font-size: 0.6rem;
margin-left: 5px;
border: 1px solid black;
padding: 1px 3px;
font-weight: bold;
font-family: sans-serif;
vertical-align: text-top;
display: inline-block;
background: white;
color: black;
}The project uses a custom i18n.js loader.
| Attribute | Usage |
|---|---|
data-i18n="key" |
Sets innerText |
data-i18n-html="key" |
Sets innerHTML (Careful with XSS) |
data-i18n-placeholder="key" |
Sets input placeholder |
data-i18n-title="key" |
Sets element title tooltip |
data-i18n-only="lang" |
Shows element only for specific lang code (e.g., "en") |
Icons are stored as raw SVG strings in icons.js.
- Size: Designed for 32x32 pixel grid.
- Stroke:
stroke-width="2"(Standard) or"1.5"for detail. - Style:
fill="none"stroke="black"ORfill="black"stroke="none".
- Engine: V8 (Ignition Interpreter ONLY).
- Flag:
--js-flags="jitless". - Impact: 5x-10x slower CPU performance than standard mobile browsers.
- Rule: Avoid heavy computation, crypto, or massive data parsing on the main thread.
- Method: Use
data-i18nattributes for all text content. - Library:
js/i18n.jshandles replacement automatically.
- Meta Tag:
user-scalable=no. - Sticky Positioning: AVOID
position: stickyorfixedheader/footers. They cause "checkerboarding" artifacts during E-ink page refreshes. - Touch Targets: Minimum 48x48px.
- Persistence:
localStorageis available but volatile. - Limit: 64MB Global Cache Limit. If exceeded, the OS performs
rm -rfon the entire cache directory at launch. - Sync: Rely on Firebase Firestore for critical data; do not trust
localStoragefor long-term storage.
- Constraint: The Kindle browser (
IntlAPI) often defaults to UTC or ignores the system timezone configuration. - Impact:
new Date().getHours()return UTC hours, not local wall time.toLocaleString()often fails to apply named timezones (e.g. "Australia/Sydney"). - Date Formatting: The Kindle browser does not reliably support
dateStyle/timeStyleoptions intoLocaleString()/Intl.DateTimeFormat. Output may differ from desktop browsers or be ignored entirely. Always use manual string formatting (e.g., building"Feb 10, 2026 at 2:42 PM"from individual date components) instead of relying on these options. - Solution:
- Avoid relying on
Intl.DateTimeFormatfor timezone shifting. - Use a Manual Offset strategy: Store a numeric offset (e.g.,
+11) and mathematically shift the timestamp before displaying. - Use the
time.jshelperrekindleGetZonedDate()which handles this shim.
- Avoid relying on
- Images: Use WebP or SVG. They are fully supported and perform best.
- Modals: Always stick to the
.modal-overlay/.modal-boxDOM structure found inweather.html.