Compare commits

...

3 Commits

Author SHA1 Message Date
root
b7306877f1 refactor: clean up home page UI
Some checks failed
Release / Test backend (push) Successful in 43s
Release / Check ui (push) Failing after 36s
Release / Docker (push) Has been skipped
Release / Gitea Release (push) Has been skipped
- Remove all emojis (flame icon in streak widget, 🔥 in stats footer,
  ✓ in completed badge) — they cheapened the overall feel
- Fix double carousel indicator: drop the animated progress sub-line
  below the active dot; the expanding pill shape is sufficient signal.
  Also removes the rAF animation loop and progressStart state.
- Remove 'X left' badge from Continue Reading shelf cards
- Remove 'X chapters ahead' text from hero card info row
2026-04-08 20:54:37 +05:00
root
0723049e0c ci: fail if Paraglide generated files are out of sync with messages JSON
All checks were successful
Release / Test backend (push) Successful in 41s
Release / Check ui (push) Successful in 1m46s
Release / Docker (push) Successful in 5m39s
Release / Gitea Release (push) Successful in 29s
2026-04-08 20:42:21 +05:00
root
b206994459 fix: generate missing Paraglide message modules for new i18n keys
Hand-authored the 12 missing .js message modules and updated _index.js
since npm/paraglide-js codegen cannot run in this environment.
These are equivalent to what 'npm run paraglide' would generate.

Going forward: run 'npm run paraglide' after adding keys to messages/*.json.
2026-04-08 20:40:58 +05:00
15 changed files with 500 additions and 50 deletions

View File

@@ -63,6 +63,9 @@ jobs:
- name: Install dependencies
run: npm ci
- name: Check Paraglide codegen is up to date
run: npm run paraglide && git diff --exit-code src/lib/paraglide/
- name: Type check
run: npm run check

View File

@@ -405,6 +405,18 @@ export * from './admin_audio_no_cache_results.js'
export * from './admin_changelog_gitea.js'
export * from './admin_changelog_no_releases.js'
export * from './admin_changelog_load_error.js'
export * from './admin_translation_page_title.js'
export * from './admin_translation_heading.js'
export * from './admin_translation_tab_enqueue.js'
export * from './admin_translation_tab_jobs.js'
export * from './admin_translation_filter_placeholder.js'
export * from './admin_translation_no_matching.js'
export * from './admin_translation_no_jobs.js'
export * from './admin_ai_jobs_page_title.js'
export * from './admin_ai_jobs_heading.js'
export * from './admin_ai_jobs_subheading.js'
export * from './admin_text_gen_page_title.js'
export * from './admin_text_gen_heading.js'
export * from './comments_top.js'
export * from './comments_new.js'
export * from './comments_posting.js'

View File

@@ -0,0 +1,40 @@
/* eslint-disable */
import { getLocale, experimentalStaticLocale } from '../runtime.js';
/** @typedef {import('../runtime.js').LocalizedString} LocalizedString */
/** @typedef {{}} Admin_Ai_Jobs_HeadingInputs */
const en_admin_ai_jobs_heading = /** @type {(inputs: Admin_Ai_Jobs_HeadingInputs) => LocalizedString} */ () => {
return /** @type {LocalizedString} */ (`AI Jobs`)
};
const ru_admin_ai_jobs_heading = /** @type {(inputs: Admin_Ai_Jobs_HeadingInputs) => LocalizedString} */ () => {
return /** @type {LocalizedString} */ (`AI Jobs`)
};
const id_admin_ai_jobs_heading = /** @type {(inputs: Admin_Ai_Jobs_HeadingInputs) => LocalizedString} */ () => {
return /** @type {LocalizedString} */ (`AI Jobs`)
};
const pt_admin_ai_jobs_heading = /** @type {(inputs: Admin_Ai_Jobs_HeadingInputs) => LocalizedString} */ () => {
return /** @type {LocalizedString} */ (`AI Jobs`)
};
const fr_admin_ai_jobs_heading = /** @type {(inputs: Admin_Ai_Jobs_HeadingInputs) => LocalizedString} */ () => {
return /** @type {LocalizedString} */ (`AI Jobs`)
};
/**
* @param {Admin_Ai_Jobs_HeadingInputs} inputs
* @param {{ locale?: "en" | "ru" | "id" | "pt" | "fr" }} options
* @returns {LocalizedString}
*/
export const admin_ai_jobs_heading = /** @type {((inputs?: Admin_Ai_Jobs_HeadingInputs, options?: { locale?: "en" | "ru" | "id" | "pt" | "fr" }) => LocalizedString) & import('../runtime.js').MessageMetadata<Admin_Ai_Jobs_HeadingInputs, { locale?: "en" | "ru" | "id" | "pt" | "fr" }, {}>} */ ((inputs = {}, options = {}) => {
const locale = experimentalStaticLocale ?? options.locale ?? getLocale()
if (locale === "en") return en_admin_ai_jobs_heading(inputs)
if (locale === "ru") return ru_admin_ai_jobs_heading(inputs)
if (locale === "id") return id_admin_ai_jobs_heading(inputs)
if (locale === "pt") return pt_admin_ai_jobs_heading(inputs)
return fr_admin_ai_jobs_heading(inputs)
});

View File

@@ -0,0 +1,40 @@
/* eslint-disable */
import { getLocale, experimentalStaticLocale } from '../runtime.js';
/** @typedef {import('../runtime.js').LocalizedString} LocalizedString */
/** @typedef {{}} Admin_Ai_Jobs_Page_TitleInputs */
const en_admin_ai_jobs_page_title = /** @type {(inputs: Admin_Ai_Jobs_Page_TitleInputs) => LocalizedString} */ () => {
return /** @type {LocalizedString} */ (`AI Jobs — Admin`)
};
const ru_admin_ai_jobs_page_title = /** @type {(inputs: Admin_Ai_Jobs_Page_TitleInputs) => LocalizedString} */ () => {
return /** @type {LocalizedString} */ (`AI Jobs — Admin`)
};
const id_admin_ai_jobs_page_title = /** @type {(inputs: Admin_Ai_Jobs_Page_TitleInputs) => LocalizedString} */ () => {
return /** @type {LocalizedString} */ (`AI Jobs — Admin`)
};
const pt_admin_ai_jobs_page_title = /** @type {(inputs: Admin_Ai_Jobs_Page_TitleInputs) => LocalizedString} */ () => {
return /** @type {LocalizedString} */ (`AI Jobs — Admin`)
};
const fr_admin_ai_jobs_page_title = /** @type {(inputs: Admin_Ai_Jobs_Page_TitleInputs) => LocalizedString} */ () => {
return /** @type {LocalizedString} */ (`AI Jobs — Admin`)
};
/**
* @param {Admin_Ai_Jobs_Page_TitleInputs} inputs
* @param {{ locale?: "en" | "ru" | "id" | "pt" | "fr" }} options
* @returns {LocalizedString}
*/
export const admin_ai_jobs_page_title = /** @type {((inputs?: Admin_Ai_Jobs_Page_TitleInputs, options?: { locale?: "en" | "ru" | "id" | "pt" | "fr" }) => LocalizedString) & import('../runtime.js').MessageMetadata<Admin_Ai_Jobs_Page_TitleInputs, { locale?: "en" | "ru" | "id" | "pt" | "fr" }, {}>} */ ((inputs = {}, options = {}) => {
const locale = experimentalStaticLocale ?? options.locale ?? getLocale()
if (locale === "en") return en_admin_ai_jobs_page_title(inputs)
if (locale === "ru") return ru_admin_ai_jobs_page_title(inputs)
if (locale === "id") return id_admin_ai_jobs_page_title(inputs)
if (locale === "pt") return pt_admin_ai_jobs_page_title(inputs)
return fr_admin_ai_jobs_page_title(inputs)
});

View File

@@ -0,0 +1,40 @@
/* eslint-disable */
import { getLocale, experimentalStaticLocale } from '../runtime.js';
/** @typedef {import('../runtime.js').LocalizedString} LocalizedString */
/** @typedef {{}} Admin_Ai_Jobs_SubheadingInputs */
const en_admin_ai_jobs_subheading = /** @type {(inputs: Admin_Ai_Jobs_SubheadingInputs) => LocalizedString} */ () => {
return /** @type {LocalizedString} */ (`Background AI generation tasks`)
};
const ru_admin_ai_jobs_subheading = /** @type {(inputs: Admin_Ai_Jobs_SubheadingInputs) => LocalizedString} */ () => {
return /** @type {LocalizedString} */ (`Background AI generation tasks`)
};
const id_admin_ai_jobs_subheading = /** @type {(inputs: Admin_Ai_Jobs_SubheadingInputs) => LocalizedString} */ () => {
return /** @type {LocalizedString} */ (`Background AI generation tasks`)
};
const pt_admin_ai_jobs_subheading = /** @type {(inputs: Admin_Ai_Jobs_SubheadingInputs) => LocalizedString} */ () => {
return /** @type {LocalizedString} */ (`Background AI generation tasks`)
};
const fr_admin_ai_jobs_subheading = /** @type {(inputs: Admin_Ai_Jobs_SubheadingInputs) => LocalizedString} */ () => {
return /** @type {LocalizedString} */ (`Background AI generation tasks`)
};
/**
* @param {Admin_Ai_Jobs_SubheadingInputs} inputs
* @param {{ locale?: "en" | "ru" | "id" | "pt" | "fr" }} options
* @returns {LocalizedString}
*/
export const admin_ai_jobs_subheading = /** @type {((inputs?: Admin_Ai_Jobs_SubheadingInputs, options?: { locale?: "en" | "ru" | "id" | "pt" | "fr" }) => LocalizedString) & import('../runtime.js').MessageMetadata<Admin_Ai_Jobs_SubheadingInputs, { locale?: "en" | "ru" | "id" | "pt" | "fr" }, {}>} */ ((inputs = {}, options = {}) => {
const locale = experimentalStaticLocale ?? options.locale ?? getLocale()
if (locale === "en") return en_admin_ai_jobs_subheading(inputs)
if (locale === "ru") return ru_admin_ai_jobs_subheading(inputs)
if (locale === "id") return id_admin_ai_jobs_subheading(inputs)
if (locale === "pt") return pt_admin_ai_jobs_subheading(inputs)
return fr_admin_ai_jobs_subheading(inputs)
});

View File

@@ -0,0 +1,40 @@
/* eslint-disable */
import { getLocale, experimentalStaticLocale } from '../runtime.js';
/** @typedef {import('../runtime.js').LocalizedString} LocalizedString */
/** @typedef {{}} Admin_Text_Gen_HeadingInputs */
const en_admin_text_gen_heading = /** @type {(inputs: Admin_Text_Gen_HeadingInputs) => LocalizedString} */ () => {
return /** @type {LocalizedString} */ (`Text Generation`)
};
const ru_admin_text_gen_heading = /** @type {(inputs: Admin_Text_Gen_HeadingInputs) => LocalizedString} */ () => {
return /** @type {LocalizedString} */ (`Text Generation`)
};
const id_admin_text_gen_heading = /** @type {(inputs: Admin_Text_Gen_HeadingInputs) => LocalizedString} */ () => {
return /** @type {LocalizedString} */ (`Text Generation`)
};
const pt_admin_text_gen_heading = /** @type {(inputs: Admin_Text_Gen_HeadingInputs) => LocalizedString} */ () => {
return /** @type {LocalizedString} */ (`Text Generation`)
};
const fr_admin_text_gen_heading = /** @type {(inputs: Admin_Text_Gen_HeadingInputs) => LocalizedString} */ () => {
return /** @type {LocalizedString} */ (`Text Generation`)
};
/**
* @param {Admin_Text_Gen_HeadingInputs} inputs
* @param {{ locale?: "en" | "ru" | "id" | "pt" | "fr" }} options
* @returns {LocalizedString}
*/
export const admin_text_gen_heading = /** @type {((inputs?: Admin_Text_Gen_HeadingInputs, options?: { locale?: "en" | "ru" | "id" | "pt" | "fr" }) => LocalizedString) & import('../runtime.js').MessageMetadata<Admin_Text_Gen_HeadingInputs, { locale?: "en" | "ru" | "id" | "pt" | "fr" }, {}>} */ ((inputs = {}, options = {}) => {
const locale = experimentalStaticLocale ?? options.locale ?? getLocale()
if (locale === "en") return en_admin_text_gen_heading(inputs)
if (locale === "ru") return ru_admin_text_gen_heading(inputs)
if (locale === "id") return id_admin_text_gen_heading(inputs)
if (locale === "pt") return pt_admin_text_gen_heading(inputs)
return fr_admin_text_gen_heading(inputs)
});

View File

@@ -0,0 +1,40 @@
/* eslint-disable */
import { getLocale, experimentalStaticLocale } from '../runtime.js';
/** @typedef {import('../runtime.js').LocalizedString} LocalizedString */
/** @typedef {{}} Admin_Text_Gen_Page_TitleInputs */
const en_admin_text_gen_page_title = /** @type {(inputs: Admin_Text_Gen_Page_TitleInputs) => LocalizedString} */ () => {
return /** @type {LocalizedString} */ (`Text Gen — Admin`)
};
const ru_admin_text_gen_page_title = /** @type {(inputs: Admin_Text_Gen_Page_TitleInputs) => LocalizedString} */ () => {
return /** @type {LocalizedString} */ (`Text Gen — Admin`)
};
const id_admin_text_gen_page_title = /** @type {(inputs: Admin_Text_Gen_Page_TitleInputs) => LocalizedString} */ () => {
return /** @type {LocalizedString} */ (`Text Gen — Admin`)
};
const pt_admin_text_gen_page_title = /** @type {(inputs: Admin_Text_Gen_Page_TitleInputs) => LocalizedString} */ () => {
return /** @type {LocalizedString} */ (`Text Gen — Admin`)
};
const fr_admin_text_gen_page_title = /** @type {(inputs: Admin_Text_Gen_Page_TitleInputs) => LocalizedString} */ () => {
return /** @type {LocalizedString} */ (`Text Gen — Admin`)
};
/**
* @param {Admin_Text_Gen_Page_TitleInputs} inputs
* @param {{ locale?: "en" | "ru" | "id" | "pt" | "fr" }} options
* @returns {LocalizedString}
*/
export const admin_text_gen_page_title = /** @type {((inputs?: Admin_Text_Gen_Page_TitleInputs, options?: { locale?: "en" | "ru" | "id" | "pt" | "fr" }) => LocalizedString) & import('../runtime.js').MessageMetadata<Admin_Text_Gen_Page_TitleInputs, { locale?: "en" | "ru" | "id" | "pt" | "fr" }, {}>} */ ((inputs = {}, options = {}) => {
const locale = experimentalStaticLocale ?? options.locale ?? getLocale()
if (locale === "en") return en_admin_text_gen_page_title(inputs)
if (locale === "ru") return ru_admin_text_gen_page_title(inputs)
if (locale === "id") return id_admin_text_gen_page_title(inputs)
if (locale === "pt") return pt_admin_text_gen_page_title(inputs)
return fr_admin_text_gen_page_title(inputs)
});

View File

@@ -0,0 +1,40 @@
/* eslint-disable */
import { getLocale, experimentalStaticLocale } from '../runtime.js';
/** @typedef {import('../runtime.js').LocalizedString} LocalizedString */
/** @typedef {{}} Admin_Translation_Filter_PlaceholderInputs */
const en_admin_translation_filter_placeholder = /** @type {(inputs: Admin_Translation_Filter_PlaceholderInputs) => LocalizedString} */ () => {
return /** @type {LocalizedString} */ (`Filter by slug, lang, or status…`)
};
const ru_admin_translation_filter_placeholder = /** @type {(inputs: Admin_Translation_Filter_PlaceholderInputs) => LocalizedString} */ () => {
return /** @type {LocalizedString} */ (`Filter by slug, lang, or status…`)
};
const id_admin_translation_filter_placeholder = /** @type {(inputs: Admin_Translation_Filter_PlaceholderInputs) => LocalizedString} */ () => {
return /** @type {LocalizedString} */ (`Filter by slug, lang, or status…`)
};
const pt_admin_translation_filter_placeholder = /** @type {(inputs: Admin_Translation_Filter_PlaceholderInputs) => LocalizedString} */ () => {
return /** @type {LocalizedString} */ (`Filter by slug, lang, or status…`)
};
const fr_admin_translation_filter_placeholder = /** @type {(inputs: Admin_Translation_Filter_PlaceholderInputs) => LocalizedString} */ () => {
return /** @type {LocalizedString} */ (`Filter by slug, lang, or status…`)
};
/**
* @param {Admin_Translation_Filter_PlaceholderInputs} inputs
* @param {{ locale?: "en" | "ru" | "id" | "pt" | "fr" }} options
* @returns {LocalizedString}
*/
export const admin_translation_filter_placeholder = /** @type {((inputs?: Admin_Translation_Filter_PlaceholderInputs, options?: { locale?: "en" | "ru" | "id" | "pt" | "fr" }) => LocalizedString) & import('../runtime.js').MessageMetadata<Admin_Translation_Filter_PlaceholderInputs, { locale?: "en" | "ru" | "id" | "pt" | "fr" }, {}>} */ ((inputs = {}, options = {}) => {
const locale = experimentalStaticLocale ?? options.locale ?? getLocale()
if (locale === "en") return en_admin_translation_filter_placeholder(inputs)
if (locale === "ru") return ru_admin_translation_filter_placeholder(inputs)
if (locale === "id") return id_admin_translation_filter_placeholder(inputs)
if (locale === "pt") return pt_admin_translation_filter_placeholder(inputs)
return fr_admin_translation_filter_placeholder(inputs)
});

View File

@@ -0,0 +1,40 @@
/* eslint-disable */
import { getLocale, experimentalStaticLocale } from '../runtime.js';
/** @typedef {import('../runtime.js').LocalizedString} LocalizedString */
/** @typedef {{}} Admin_Translation_HeadingInputs */
const en_admin_translation_heading = /** @type {(inputs: Admin_Translation_HeadingInputs) => LocalizedString} */ () => {
return /** @type {LocalizedString} */ (`Machine Translation`)
};
const ru_admin_translation_heading = /** @type {(inputs: Admin_Translation_HeadingInputs) => LocalizedString} */ () => {
return /** @type {LocalizedString} */ (`Machine Translation`)
};
const id_admin_translation_heading = /** @type {(inputs: Admin_Translation_HeadingInputs) => LocalizedString} */ () => {
return /** @type {LocalizedString} */ (`Machine Translation`)
};
const pt_admin_translation_heading = /** @type {(inputs: Admin_Translation_HeadingInputs) => LocalizedString} */ () => {
return /** @type {LocalizedString} */ (`Machine Translation`)
};
const fr_admin_translation_heading = /** @type {(inputs: Admin_Translation_HeadingInputs) => LocalizedString} */ () => {
return /** @type {LocalizedString} */ (`Machine Translation`)
};
/**
* @param {Admin_Translation_HeadingInputs} inputs
* @param {{ locale?: "en" | "ru" | "id" | "pt" | "fr" }} options
* @returns {LocalizedString}
*/
export const admin_translation_heading = /** @type {((inputs?: Admin_Translation_HeadingInputs, options?: { locale?: "en" | "ru" | "id" | "pt" | "fr" }) => LocalizedString) & import('../runtime.js').MessageMetadata<Admin_Translation_HeadingInputs, { locale?: "en" | "ru" | "id" | "pt" | "fr" }, {}>} */ ((inputs = {}, options = {}) => {
const locale = experimentalStaticLocale ?? options.locale ?? getLocale()
if (locale === "en") return en_admin_translation_heading(inputs)
if (locale === "ru") return ru_admin_translation_heading(inputs)
if (locale === "id") return id_admin_translation_heading(inputs)
if (locale === "pt") return pt_admin_translation_heading(inputs)
return fr_admin_translation_heading(inputs)
});

View File

@@ -0,0 +1,40 @@
/* eslint-disable */
import { getLocale, experimentalStaticLocale } from '../runtime.js';
/** @typedef {import('../runtime.js').LocalizedString} LocalizedString */
/** @typedef {{}} Admin_Translation_No_JobsInputs */
const en_admin_translation_no_jobs = /** @type {(inputs: Admin_Translation_No_JobsInputs) => LocalizedString} */ () => {
return /** @type {LocalizedString} */ (`No translation jobs yet.`)
};
const ru_admin_translation_no_jobs = /** @type {(inputs: Admin_Translation_No_JobsInputs) => LocalizedString} */ () => {
return /** @type {LocalizedString} */ (`No translation jobs yet.`)
};
const id_admin_translation_no_jobs = /** @type {(inputs: Admin_Translation_No_JobsInputs) => LocalizedString} */ () => {
return /** @type {LocalizedString} */ (`No translation jobs yet.`)
};
const pt_admin_translation_no_jobs = /** @type {(inputs: Admin_Translation_No_JobsInputs) => LocalizedString} */ () => {
return /** @type {LocalizedString} */ (`No translation jobs yet.`)
};
const fr_admin_translation_no_jobs = /** @type {(inputs: Admin_Translation_No_JobsInputs) => LocalizedString} */ () => {
return /** @type {LocalizedString} */ (`No translation jobs yet.`)
};
/**
* @param {Admin_Translation_No_JobsInputs} inputs
* @param {{ locale?: "en" | "ru" | "id" | "pt" | "fr" }} options
* @returns {LocalizedString}
*/
export const admin_translation_no_jobs = /** @type {((inputs?: Admin_Translation_No_JobsInputs, options?: { locale?: "en" | "ru" | "id" | "pt" | "fr" }) => LocalizedString) & import('../runtime.js').MessageMetadata<Admin_Translation_No_JobsInputs, { locale?: "en" | "ru" | "id" | "pt" | "fr" }, {}>} */ ((inputs = {}, options = {}) => {
const locale = experimentalStaticLocale ?? options.locale ?? getLocale()
if (locale === "en") return en_admin_translation_no_jobs(inputs)
if (locale === "ru") return ru_admin_translation_no_jobs(inputs)
if (locale === "id") return id_admin_translation_no_jobs(inputs)
if (locale === "pt") return pt_admin_translation_no_jobs(inputs)
return fr_admin_translation_no_jobs(inputs)
});

View File

@@ -0,0 +1,40 @@
/* eslint-disable */
import { getLocale, experimentalStaticLocale } from '../runtime.js';
/** @typedef {import('../runtime.js').LocalizedString} LocalizedString */
/** @typedef {{}} Admin_Translation_No_MatchingInputs */
const en_admin_translation_no_matching = /** @type {(inputs: Admin_Translation_No_MatchingInputs) => LocalizedString} */ () => {
return /** @type {LocalizedString} */ (`No matching jobs.`)
};
const ru_admin_translation_no_matching = /** @type {(inputs: Admin_Translation_No_MatchingInputs) => LocalizedString} */ () => {
return /** @type {LocalizedString} */ (`No matching jobs.`)
};
const id_admin_translation_no_matching = /** @type {(inputs: Admin_Translation_No_MatchingInputs) => LocalizedString} */ () => {
return /** @type {LocalizedString} */ (`No matching jobs.`)
};
const pt_admin_translation_no_matching = /** @type {(inputs: Admin_Translation_No_MatchingInputs) => LocalizedString} */ () => {
return /** @type {LocalizedString} */ (`No matching jobs.`)
};
const fr_admin_translation_no_matching = /** @type {(inputs: Admin_Translation_No_MatchingInputs) => LocalizedString} */ () => {
return /** @type {LocalizedString} */ (`No matching jobs.`)
};
/**
* @param {Admin_Translation_No_MatchingInputs} inputs
* @param {{ locale?: "en" | "ru" | "id" | "pt" | "fr" }} options
* @returns {LocalizedString}
*/
export const admin_translation_no_matching = /** @type {((inputs?: Admin_Translation_No_MatchingInputs, options?: { locale?: "en" | "ru" | "id" | "pt" | "fr" }) => LocalizedString) & import('../runtime.js').MessageMetadata<Admin_Translation_No_MatchingInputs, { locale?: "en" | "ru" | "id" | "pt" | "fr" }, {}>} */ ((inputs = {}, options = {}) => {
const locale = experimentalStaticLocale ?? options.locale ?? getLocale()
if (locale === "en") return en_admin_translation_no_matching(inputs)
if (locale === "ru") return ru_admin_translation_no_matching(inputs)
if (locale === "id") return id_admin_translation_no_matching(inputs)
if (locale === "pt") return pt_admin_translation_no_matching(inputs)
return fr_admin_translation_no_matching(inputs)
});

View File

@@ -0,0 +1,40 @@
/* eslint-disable */
import { getLocale, experimentalStaticLocale } from '../runtime.js';
/** @typedef {import('../runtime.js').LocalizedString} LocalizedString */
/** @typedef {{}} Admin_Translation_Page_TitleInputs */
const en_admin_translation_page_title = /** @type {(inputs: Admin_Translation_Page_TitleInputs) => LocalizedString} */ () => {
return /** @type {LocalizedString} */ (`Translation — Admin`)
};
const ru_admin_translation_page_title = /** @type {(inputs: Admin_Translation_Page_TitleInputs) => LocalizedString} */ () => {
return /** @type {LocalizedString} */ (`Translation — Admin`)
};
const id_admin_translation_page_title = /** @type {(inputs: Admin_Translation_Page_TitleInputs) => LocalizedString} */ () => {
return /** @type {LocalizedString} */ (`Translation — Admin`)
};
const pt_admin_translation_page_title = /** @type {(inputs: Admin_Translation_Page_TitleInputs) => LocalizedString} */ () => {
return /** @type {LocalizedString} */ (`Translation — Admin`)
};
const fr_admin_translation_page_title = /** @type {(inputs: Admin_Translation_Page_TitleInputs) => LocalizedString} */ () => {
return /** @type {LocalizedString} */ (`Translation — Admin`)
};
/**
* @param {Admin_Translation_Page_TitleInputs} inputs
* @param {{ locale?: "en" | "ru" | "id" | "pt" | "fr" }} options
* @returns {LocalizedString}
*/
export const admin_translation_page_title = /** @type {((inputs?: Admin_Translation_Page_TitleInputs, options?: { locale?: "en" | "ru" | "id" | "pt" | "fr" }) => LocalizedString) & import('../runtime.js').MessageMetadata<Admin_Translation_Page_TitleInputs, { locale?: "en" | "ru" | "id" | "pt" | "fr" }, {}>} */ ((inputs = {}, options = {}) => {
const locale = experimentalStaticLocale ?? options.locale ?? getLocale()
if (locale === "en") return en_admin_translation_page_title(inputs)
if (locale === "ru") return ru_admin_translation_page_title(inputs)
if (locale === "id") return id_admin_translation_page_title(inputs)
if (locale === "pt") return pt_admin_translation_page_title(inputs)
return fr_admin_translation_page_title(inputs)
});

View File

@@ -0,0 +1,40 @@
/* eslint-disable */
import { getLocale, experimentalStaticLocale } from '../runtime.js';
/** @typedef {import('../runtime.js').LocalizedString} LocalizedString */
/** @typedef {{}} Admin_Translation_Tab_EnqueueInputs */
const en_admin_translation_tab_enqueue = /** @type {(inputs: Admin_Translation_Tab_EnqueueInputs) => LocalizedString} */ () => {
return /** @type {LocalizedString} */ (`Enqueue`)
};
const ru_admin_translation_tab_enqueue = /** @type {(inputs: Admin_Translation_Tab_EnqueueInputs) => LocalizedString} */ () => {
return /** @type {LocalizedString} */ (`Enqueue`)
};
const id_admin_translation_tab_enqueue = /** @type {(inputs: Admin_Translation_Tab_EnqueueInputs) => LocalizedString} */ () => {
return /** @type {LocalizedString} */ (`Enqueue`)
};
const pt_admin_translation_tab_enqueue = /** @type {(inputs: Admin_Translation_Tab_EnqueueInputs) => LocalizedString} */ () => {
return /** @type {LocalizedString} */ (`Enqueue`)
};
const fr_admin_translation_tab_enqueue = /** @type {(inputs: Admin_Translation_Tab_EnqueueInputs) => LocalizedString} */ () => {
return /** @type {LocalizedString} */ (`Enqueue`)
};
/**
* @param {Admin_Translation_Tab_EnqueueInputs} inputs
* @param {{ locale?: "en" | "ru" | "id" | "pt" | "fr" }} options
* @returns {LocalizedString}
*/
export const admin_translation_tab_enqueue = /** @type {((inputs?: Admin_Translation_Tab_EnqueueInputs, options?: { locale?: "en" | "ru" | "id" | "pt" | "fr" }) => LocalizedString) & import('../runtime.js').MessageMetadata<Admin_Translation_Tab_EnqueueInputs, { locale?: "en" | "ru" | "id" | "pt" | "fr" }, {}>} */ ((inputs = {}, options = {}) => {
const locale = experimentalStaticLocale ?? options.locale ?? getLocale()
if (locale === "en") return en_admin_translation_tab_enqueue(inputs)
if (locale === "ru") return ru_admin_translation_tab_enqueue(inputs)
if (locale === "id") return id_admin_translation_tab_enqueue(inputs)
if (locale === "pt") return pt_admin_translation_tab_enqueue(inputs)
return fr_admin_translation_tab_enqueue(inputs)
});

View File

@@ -0,0 +1,40 @@
/* eslint-disable */
import { getLocale, experimentalStaticLocale } from '../runtime.js';
/** @typedef {import('../runtime.js').LocalizedString} LocalizedString */
/** @typedef {{}} Admin_Translation_Tab_JobsInputs */
const en_admin_translation_tab_jobs = /** @type {(inputs: Admin_Translation_Tab_JobsInputs) => LocalizedString} */ () => {
return /** @type {LocalizedString} */ (`Jobs`)
};
const ru_admin_translation_tab_jobs = /** @type {(inputs: Admin_Translation_Tab_JobsInputs) => LocalizedString} */ () => {
return /** @type {LocalizedString} */ (`Jobs`)
};
const id_admin_translation_tab_jobs = /** @type {(inputs: Admin_Translation_Tab_JobsInputs) => LocalizedString} */ () => {
return /** @type {LocalizedString} */ (`Jobs`)
};
const pt_admin_translation_tab_jobs = /** @type {(inputs: Admin_Translation_Tab_JobsInputs) => LocalizedString} */ () => {
return /** @type {LocalizedString} */ (`Jobs`)
};
const fr_admin_translation_tab_jobs = /** @type {(inputs: Admin_Translation_Tab_JobsInputs) => LocalizedString} */ () => {
return /** @type {LocalizedString} */ (`Jobs`)
};
/**
* @param {Admin_Translation_Tab_JobsInputs} inputs
* @param {{ locale?: "en" | "ru" | "id" | "pt" | "fr" }} options
* @returns {LocalizedString}
*/
export const admin_translation_tab_jobs = /** @type {((inputs?: Admin_Translation_Tab_JobsInputs, options?: { locale?: "en" | "ru" | "id" | "pt" | "fr" }) => LocalizedString) & import('../runtime.js').MessageMetadata<Admin_Translation_Tab_JobsInputs, { locale?: "en" | "ru" | "id" | "pt" | "fr" }, {}>} */ ((inputs = {}, options = {}) => {
const locale = experimentalStaticLocale ?? options.locale ?? getLocale()
if (locale === "en") return en_admin_translation_tab_jobs(inputs)
if (locale === "ru") return ru_admin_translation_tab_jobs(inputs)
if (locale === "id") return id_admin_translation_tab_jobs(inputs)
if (locale === "pt") return pt_admin_translation_tab_jobs(inputs)
return fr_admin_translation_tab_jobs(inputs)
});

View File

@@ -90,17 +90,13 @@
// Auto-advance carousel every CAROUSEL_INTERVAL ms when there are multiple books.
// autoAdvanceSeed is bumped on manual swipe/dot to restart the interval.
let autoAdvanceSeed = $state(0);
// progressStart tracks when the current interval began (for the progress bar).
let progressStart = $state(browser ? performance.now() : 0);
$effect(() => {
if (heroBooks.length <= 1) return;
const len = heroBooks.length;
void autoAdvanceSeed; // restart when seed changes
progressStart = browser ? performance.now() : 0;
const id = setInterval(() => {
heroIndex = (heroIndex + 1) % len;
progressStart = browser ? performance.now() : 0;
}, CAROUSEL_INTERVAL);
return () => clearInterval(id);
});
@@ -109,8 +105,7 @@
autoAdvanceSeed++;
}
// ── Swipe handling ───────────────────────────────────────────────────────
let swipeStartX = 0;
// ── Swipe handling ─────────────────────────────────────────────────────── let swipeStartX = 0;
function onSwipeStart(e: TouchEvent) {
swipeStartX = e.touches[0].clientX;
}
@@ -127,22 +122,6 @@
resetAutoAdvance();
}
// ── Progress bar animation ───────────────────────────────────────────────
// rAF loop drives a 0→1 progress value that resets on each advance.
let rafProgress = $state(0);
$effect(() => {
if (!browser || heroBooks.length <= 1) return;
void autoAdvanceSeed; // re-subscribe so effect re-runs on manual nav
void heroIndex;
let raf: number;
function tick() {
rafProgress = Math.min((performance.now() - progressStart) / CAROUSEL_INTERVAL, 1);
raf = requestAnimationFrame(tick);
}
raf = requestAnimationFrame(tick);
return () => cancelAnimationFrame(raf);
});
function playChapter(slug: string, chapter: number) {
audioStore.autoStartChapter = chapter;
goto(`/books/${slug}/chapters/${chapter}`);
@@ -210,10 +189,6 @@
</svg>
Listen
</button>
{#if heroBook.book.total_chapters > 0 && heroBook.chapter < heroBook.book.total_chapters}
{@const ahead = heroBook.book.total_chapters - heroBook.chapter}
<span class="text-xs text-(--color-muted) hidden sm:inline">{ahead} chapters ahead</span>
{/if}
{#each parseGenres(heroBook.book.genres).slice(0, 2) as genre}
<span class="text-xs px-2 py-1 rounded-full bg-(--color-surface-3) text-(--color-muted)">{genre}</span>
{/each}
@@ -221,7 +196,7 @@
</div>
</div>
<!-- Dot indicators with animated progress line under active dot -->
<!-- Dot indicators -->
{#if heroBooks.length > 1}
<div class="flex items-center justify-center gap-2 mt-2.5">
{#each heroBooks as _, i}
@@ -229,21 +204,10 @@
type="button"
onclick={() => heroDot(i)}
aria-label="Go to book {i + 1}"
class="relative flex flex-col items-center gap-0.5 group/dot"
>
<!-- dot -->
<span class="block rounded-full transition-all duration-300 {i === heroIndex
? 'w-4 h-1.5 bg-(--color-brand)'
: 'w-1.5 h-1.5 bg-(--color-border) group-hover/dot:bg-(--color-muted)'}"></span>
<!-- progress line — only visible under the active dot -->
{#if i === heroIndex}
<span class="absolute -bottom-1.5 left-0 h-0.5 w-full bg-(--color-border) rounded-full overflow-hidden">
<span
class="block h-full bg-(--color-brand) rounded-full"
style="width: {rafProgress * 100}%"
></span>
</span>
{/if}
: 'w-1.5 h-1.5 bg-(--color-border) hover:bg-(--color-muted)'}"></span>
</button>
{/each}
</div>
@@ -256,9 +220,6 @@
{#if streak > 0}
<div class="mb-6 flex items-center gap-3 flex-wrap text-sm">
<span class="inline-flex items-center gap-1.5 px-3 py-1.5 rounded-lg bg-(--color-surface-2) border border-(--color-border)">
<svg class="w-4 h-4 text-orange-400" fill="currentColor" viewBox="0 0 24 24">
<path d="M13.5 0.67s.74 2.65.74 4.8c0 2.06-1.35 3.73-3.41 3.73-2.07 0-3.63-1.67-3.63-3.73l.03-.36C5.21 7.51 4 10.62 4 14c0 4.42 3.58 8 8 8s8-3.58 8-8C20 8.61 17.41 3.8 13.5.67zM11.71 19c-1.78 0-3.22-1.4-3.22-3.14 0-1.62 1.05-2.76 2.81-3.12 1.77-.36 3.6-1.21 4.62-2.58.39 1.29.59 2.65.59 4.04 0 2.65-2.15 4.8-4.8 4.8z"/>
</svg>
<span class="font-semibold text-(--color-text)">{streak}</span>
<span class="text-(--color-muted)">day{streak !== 1 ? 's' : ''} reading</span>
</span>
@@ -293,12 +254,6 @@
<span class="absolute bottom-1.5 right-1.5 text-xs bg-(--color-brand) text-(--color-surface) font-bold px-1.5 py-0.5 rounded">
{m.home_chapter_badge({ n: String(chapter) })}
</span>
<!-- Chapters ahead badge -->
{#if book.total_chapters > 0 && chapter < book.total_chapters}
<span class="absolute top-1.5 left-1.5 text-xs bg-black/60 text-white font-medium px-1.5 py-0.5 rounded">
{book.total_chapters - chapter} left
</span>
{/if}
</div>
</a>
<!-- Listen button (hover overlay) -->
@@ -338,7 +293,7 @@
<svg class="w-8 h-8 text-(--color-muted)" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="1.5" d="M12 6.253v13m0-13C10.832 5.477 9.246 5 7.5 5S4.168 5.477 3 6.253v13C4.168 18.477 5.754 18 7.5 18s3.332.477 4.5 1.253m0-13C13.168 5.477 14.754 5 16.5 5c1.747 0 3.332.477 4.5 1.253v13C19.832 18.477 18.247 18 16.5 18c-1.746 0-3.332.477-4.5 1.253"/></svg>
</div>
{/if}
<span class="absolute top-1.5 right-1.5 text-xs bg-green-600/90 text-white font-bold px-1.5 py-0.5 rounded">Done</span>
<span class="absolute top-1.5 right-1.5 text-xs bg-green-600/90 text-white font-bold px-1.5 py-0.5 rounded">Done</span>
</div>
<div class="p-2">
<h3 class="text-xs font-semibold text-(--color-text) line-clamp-2 leading-snug">{book.title ?? ''}</h3>
@@ -597,6 +552,6 @@
<span><span class="font-semibold text-(--color-text)">{data.stats.totalChapters.toLocaleString()}</span> {m.home_stat_chapters()}</span>
{#if streak > 0}
<span class="w-px h-4 bg-(--color-border)"></span>
<span><span class="font-semibold text-(--color-text)">{streak}</span> day streak 🔥</span>
<span><span class="font-semibold text-(--color-text)">{streak}</span> day streak</span>
{/if}
</div>