diff --git a/.gitignore b/.gitignore
index af52d23..c8fbb05 100644
--- a/.gitignore
+++ b/.gitignore
@@ -10,3 +10,6 @@ captures/
*.apk
*.aab
*.keystore
+node_modules/
+/package-lock.json
+/package.json
diff --git a/anixart-tizen/assets/fonts/product_sans_medium.ttf b/anixart-tizen/assets/fonts/product_sans_medium.ttf
new file mode 100644
index 0000000..fd818d6
Binary files /dev/null and b/anixart-tizen/assets/fonts/product_sans_medium.ttf differ
diff --git a/anixart-tizen/assets/fonts/proxima_nova_bold.otf b/anixart-tizen/assets/fonts/proxima_nova_bold.otf
new file mode 100644
index 0000000..1ea7753
Binary files /dev/null and b/anixart-tizen/assets/fonts/proxima_nova_bold.otf differ
diff --git a/anixart-tizen/assets/fonts/roboto_medium.ttf b/anixart-tizen/assets/fonts/roboto_medium.ttf
new file mode 100644
index 0000000..ac0f908
Binary files /dev/null and b/anixart-tizen/assets/fonts/roboto_medium.ttf differ
diff --git a/anixart-tizen/assets/fonts/roboto_medium_numbers.ttf b/anixart-tizen/assets/fonts/roboto_medium_numbers.ttf
new file mode 100644
index 0000000..b61ac79
Binary files /dev/null and b/anixart-tizen/assets/fonts/roboto_medium_numbers.ttf differ
diff --git a/anixart-tizen/assets/fonts/ytsans_medium.ttf b/anixart-tizen/assets/fonts/ytsans_medium.ttf
new file mode 100644
index 0000000..c6c02af
Binary files /dev/null and b/anixart-tizen/assets/fonts/ytsans_medium.ttf differ
diff --git a/anixart-tizen/assets/icons/icon.png b/anixart-tizen/assets/icons/icon.png
new file mode 100644
index 0000000..ab74d2d
Binary files /dev/null and b/anixart-tizen/assets/icons/icon.png differ
diff --git a/anixart-tizen/assets/icons/logo_splash.png b/anixart-tizen/assets/icons/logo_splash.png
new file mode 100644
index 0000000..cca89af
Binary files /dev/null and b/anixart-tizen/assets/icons/logo_splash.png differ
diff --git a/anixart-tizen/assets/icons/logo_splash_dark.png b/anixart-tizen/assets/icons/logo_splash_dark.png
new file mode 100644
index 0000000..33c8448
Binary files /dev/null and b/anixart-tizen/assets/icons/logo_splash_dark.png differ
diff --git a/anixart-tizen/config.xml b/anixart-tizen/config.xml
new file mode 100644
index 0000000..19dc22d
--- /dev/null
+++ b/anixart-tizen/config.xml
@@ -0,0 +1,16 @@
+
+
+
+
+
+
+ Anixart
+
+
+
+
+
+
+
+
+
diff --git a/anixart-tizen/index.html b/anixart-tizen/index.html
new file mode 100644
index 0000000..49017e9
--- /dev/null
+++ b/anixart-tizen/index.html
@@ -0,0 +1,25 @@
+
+
+
+
+
+ Anixart
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/anixart-tizen/package.json b/anixart-tizen/package.json
new file mode 100644
index 0000000..45e35b4
--- /dev/null
+++ b/anixart-tizen/package.json
@@ -0,0 +1,10 @@
+{
+ "name": "anixart-tizen",
+ "version": "1.0.0",
+ "description": "Anixart for Samsung Tizen TV",
+ "scripts": {
+ "build": "echo 'Static project — no build step required'",
+ "serve": "npx http-server . -p 8080 -c-1",
+ "package": "zip -r anixart-tizen.wgt config.xml index.html src/ styles/ assets/ -x '*.DS_Store'"
+ }
+}
diff --git a/anixart-tizen/src/api/auth.js b/anixart-tizen/src/api/auth.js
new file mode 100644
index 0000000..f6abcbe
--- /dev/null
+++ b/anixart-tizen/src/api/auth.js
@@ -0,0 +1,34 @@
+var AuthApi = {
+ STATUS_OK: 0,
+ STATUS_INVALID_LOGIN: 2,
+ STATUS_INVALID_PASSWORD: 3,
+
+ signIn: function(login, password) {
+ return ApiClient.post('auth/signIn', {
+ formData: {
+ login: login,
+ password: password
+ }
+ }).then(function(response) {
+ if (response.profileToken && response.profileToken.token) {
+ Storage.setToken(response.profileToken.token);
+ Storage.setTokenId(response.profileToken.id);
+ }
+ if (response.profile) {
+ Storage.setProfile(response.profile);
+ }
+ return response;
+ });
+ },
+
+ getErrorMessage: function(status) {
+ switch (status) {
+ case this.STATUS_INVALID_LOGIN:
+ return 'Неверная почта или никнейм';
+ case this.STATUS_INVALID_PASSWORD:
+ return 'Неверный пароль';
+ default:
+ return 'Ошибка входа';
+ }
+ }
+};
diff --git a/anixart-tizen/src/api/client.js b/anixart-tizen/src/api/client.js
new file mode 100644
index 0000000..1eed8a5
--- /dev/null
+++ b/anixart-tizen/src/api/client.js
@@ -0,0 +1,75 @@
+var ApiClient = {
+ BASE_URL: 'https://api-s.anixsekai.com/',
+
+ request: function(method, endpoint, options) {
+ options = options || {};
+ var url = this.BASE_URL + endpoint;
+
+ if (options.token) {
+ url += (url.indexOf('?') === -1 ? '?' : '&') + 'token=' + encodeURIComponent(options.token);
+ }
+
+ if (options.queryParams) {
+ for (var key in options.queryParams) {
+ if (options.queryParams.hasOwnProperty(key)) {
+ url += (url.indexOf('?') === -1 ? '?' : '&') + encodeURIComponent(key) + '=' + encodeURIComponent(options.queryParams[key]);
+ }
+ }
+ }
+
+ var xhr = new XMLHttpRequest();
+ xhr.open(method, url, true);
+
+ if (options.formData) {
+ xhr.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded');
+ } else if (options.json) {
+ xhr.setRequestHeader('Content-Type', 'application/json');
+ }
+
+ return new Promise(function(resolve, reject) {
+ xhr.onload = function() {
+ if (xhr.status >= 200 && xhr.status < 300) {
+ try {
+ resolve(JSON.parse(xhr.responseText));
+ } catch (e) {
+ resolve(xhr.responseText);
+ }
+ } else {
+ reject({ status: xhr.status, text: xhr.responseText });
+ }
+ };
+
+ xhr.onerror = function() {
+ reject({ status: 0, text: 'Network error' });
+ };
+
+ xhr.ontimeout = function() {
+ reject({ status: 0, text: 'Request timeout' });
+ };
+
+ xhr.timeout = 15000;
+
+ if (options.formData) {
+ var parts = [];
+ for (var key in options.formData) {
+ if (options.formData.hasOwnProperty(key)) {
+ parts.push(encodeURIComponent(key) + '=' + encodeURIComponent(options.formData[key]));
+ }
+ }
+ xhr.send(parts.join('&'));
+ } else if (options.json) {
+ xhr.send(JSON.stringify(options.json));
+ } else {
+ xhr.send();
+ }
+ });
+ },
+
+ post: function(endpoint, options) {
+ return this.request('POST', endpoint, options);
+ },
+
+ get: function(endpoint, options) {
+ return this.request('GET', endpoint, options);
+ }
+};
diff --git a/anixart-tizen/src/api/discover.js b/anixart-tizen/src/api/discover.js
new file mode 100644
index 0000000..e3e907e
--- /dev/null
+++ b/anixart-tizen/src/api/discover.js
@@ -0,0 +1,28 @@
+var DiscoverApi = {
+ getInteresting: function() {
+ return ApiClient.post('discover/interesting');
+ },
+
+ getRecommendations: function(page, previousPage, token) {
+ return ApiClient.post('discover/recommendations/' + page, {
+ token: token,
+ queryParams: { previous_page: previousPage }
+ });
+ },
+
+ getWatching: function(page, token) {
+ return ApiClient.post('discover/watching/' + page, {
+ token: token
+ });
+ },
+
+ getDiscussing: function(token) {
+ return ApiClient.post('discover/discussing', {
+ token: token
+ });
+ },
+
+ getComments: function() {
+ return ApiClient.post('discover/comments');
+ }
+};
diff --git a/anixart-tizen/src/api/release.js b/anixart-tizen/src/api/release.js
new file mode 100644
index 0000000..b95a726
--- /dev/null
+++ b/anixart-tizen/src/api/release.js
@@ -0,0 +1,20 @@
+var ReleaseApi = {
+ getRelease: function(releaseId, token) {
+ return ApiClient.post('release/' + releaseId, {
+ token: token
+ });
+ },
+
+ getEpisodes: function(releaseId, sourceId, token) {
+ return ApiClient.post('release/' + releaseId + '/episode', {
+ token: token,
+ queryParams: sourceId ? { sourceId: sourceId } : undefined
+ });
+ },
+
+ getSources: function(releaseId, token) {
+ return ApiClient.post('release/' + releaseId + '/source', {
+ token: token
+ });
+ }
+};
diff --git a/anixart-tizen/src/app.js b/anixart-tizen/src/app.js
new file mode 100644
index 0000000..c8d1c6b
--- /dev/null
+++ b/anixart-tizen/src/app.js
@@ -0,0 +1,75 @@
+var App = {
+ history: [],
+ currentScreen: null,
+
+ init: function() {
+ document.documentElement.setAttribute('data-theme', Storage.getTheme());
+
+ FocusManager.init();
+
+ if (typeof tizen !== 'undefined') {
+ try {
+ tizen.tvinputdevice.registerKeyBatch([
+ 'MediaPlay', 'MediaPause', 'MediaPlayPause',
+ 'MediaStop', 'MediaFastForward', 'MediaRewind'
+ ]);
+ } catch (e) {}
+ }
+
+ if (Storage.isLoggedIn()) {
+ this.showScreen('home');
+ } else {
+ this.showScreen('login');
+ }
+ },
+
+ showScreen: function(name, params) {
+ if (this.currentScreen && this.currentScreen !== name) {
+ this.history.push({ name: this.currentScreen, params: this.currentParams });
+ }
+ this.currentScreen = name;
+ this.currentParams = params;
+
+ switch (name) {
+ case 'login':
+ LoginScreen.render();
+ break;
+ case 'home':
+ HomeScreen.render();
+ break;
+ case 'details':
+ DetailsScreen.render(params);
+ break;
+ }
+ },
+
+ goBack: function() {
+ if (this.history.length > 0) {
+ var prev = this.history.pop();
+ this.currentScreen = prev.name;
+ this.currentParams = prev.params;
+ switch (prev.name) {
+ case 'login':
+ LoginScreen.render();
+ break;
+ case 'home':
+ HomeScreen.render();
+ break;
+ case 'details':
+ DetailsScreen.render(prev.params);
+ break;
+ }
+ } else if (this.currentScreen !== 'home' && Storage.isLoggedIn()) {
+ this.currentScreen = 'home';
+ HomeScreen.render();
+ } else {
+ if (typeof tizen !== 'undefined') {
+ tizen.application.getCurrentApplication().exit();
+ }
+ }
+ }
+};
+
+document.addEventListener('DOMContentLoaded', function() {
+ App.init();
+});
diff --git a/anixart-tizen/src/components/bottom-nav.js b/anixart-tizen/src/components/bottom-nav.js
new file mode 100644
index 0000000..134d070
--- /dev/null
+++ b/anixart-tizen/src/components/bottom-nav.js
@@ -0,0 +1,52 @@
+var BottomNav = {
+ tabs: [
+ { id: 'home', label: 'Главная', icon: '' },
+ { id: 'discover', label: 'Обзор', icon: '' },
+ { id: 'bookmarks', label: 'Закладки', icon: '' },
+ { id: 'feed', label: 'Лента', icon: '' },
+ { id: 'profile', label: 'Профиль', icon: '' }
+ ],
+
+ render: function(activeTab) {
+ var nav = document.createElement('div');
+ nav.className = 'bottom-nav';
+
+ for (var i = 0; i < this.tabs.length; i++) {
+ var tab = this.tabs[i];
+ var btn = document.createElement('button');
+ btn.className = 'nav-tab' + (tab.id === activeTab ? ' active' : '');
+ btn.setAttribute('data-focusable', 'true');
+ btn.setAttribute('data-tab', tab.id);
+
+ var indicator = document.createElement('div');
+ indicator.className = 'nav-indicator';
+
+ var iconWrap = document.createElement('span');
+ iconWrap.className = 'nav-icon';
+ iconWrap.innerHTML = tab.icon;
+ indicator.appendChild(iconWrap);
+ btn.appendChild(indicator);
+
+ var label = document.createElement('span');
+ label.className = 'nav-label';
+ label.textContent = tab.label;
+ btn.appendChild(label);
+
+ (function(tabId) {
+ btn.addEventListener('click', function() {
+ BottomNav.onTabClick(tabId);
+ });
+ })(tab.id);
+
+ nav.appendChild(btn);
+ }
+
+ return nav;
+ },
+
+ onTabClick: function(tabId) {
+ if (tabId === 'home') {
+ App.showScreen('home');
+ }
+ }
+};
diff --git a/anixart-tizen/src/navigation/focus.js b/anixart-tizen/src/navigation/focus.js
new file mode 100644
index 0000000..b7c8610
--- /dev/null
+++ b/anixart-tizen/src/navigation/focus.js
@@ -0,0 +1,169 @@
+var FocusManager = {
+ currentFocused: null,
+ sections: [],
+
+ init: function() {
+ var self = this;
+ document.addEventListener('keydown', function(e) {
+ self.handleKey(e);
+ });
+ },
+
+ handleKey: function(e) {
+ var keyCode = e.keyCode;
+ switch (keyCode) {
+ case 37: // Left
+ e.preventDefault();
+ this.moveFocus('left');
+ break;
+ case 38: // Up
+ e.preventDefault();
+ this.moveFocus('up');
+ break;
+ case 39: // Right
+ e.preventDefault();
+ this.moveFocus('right');
+ break;
+ case 40: // Down
+ e.preventDefault();
+ this.moveFocus('down');
+ break;
+ case 13: // OK/Enter
+ e.preventDefault();
+ this.select();
+ break;
+ case 10009: // Back (Tizen)
+ case 8: // Backspace
+ e.preventDefault();
+ if (typeof App !== 'undefined') {
+ App.goBack();
+ }
+ break;
+ case 415: // Play
+ case 10252: // Play/Pause (Tizen)
+ break;
+ }
+ },
+
+ setFocus: function(el) {
+ if (this.currentFocused) {
+ this.currentFocused.classList.remove('focused');
+ }
+ this.currentFocused = el;
+ if (el) {
+ el.classList.add('focused');
+ this.scrollIntoViewSmart(el);
+ }
+ },
+
+ scrollIntoViewSmart: function(el) {
+ var scrollContainer = el.closest('.section-scroll');
+ if (scrollContainer) {
+ var elRect = el.getBoundingClientRect();
+ var contRect = scrollContainer.getBoundingClientRect();
+ if (elRect.left < contRect.left || elRect.right > contRect.right) {
+ var scrollLeft = el.offsetLeft - scrollContainer.offsetLeft - 16;
+ scrollContainer.scrollTo({ left: scrollLeft, behavior: 'smooth' });
+ }
+ }
+
+ var mainScroll = document.getElementById('main-scroll');
+ if (mainScroll) {
+ var elRect = el.getBoundingClientRect();
+ var viewHeight = window.innerHeight;
+ var navHeight = 72;
+ if (elRect.bottom > viewHeight - navHeight || elRect.top < 0) {
+ var scrollTop = el.offsetTop - mainScroll.offsetTop - 100;
+ mainScroll.scrollTo({ top: scrollTop, behavior: 'smooth' });
+ }
+ }
+ },
+
+ getFocusables: function(container) {
+ container = container || document.getElementById('app');
+ if (!container) return [];
+ return Array.prototype.slice.call(container.querySelectorAll('[data-focusable]'));
+ },
+
+ focusFirst: function(container) {
+ var items = this.getFocusables(container);
+ if (items.length > 0) {
+ this.setFocus(items[0]);
+ }
+ },
+
+ moveFocus: function(direction) {
+ if (!this.currentFocused) {
+ this.focusFirst();
+ return;
+ }
+
+ var focusables = this.getFocusables();
+ if (focusables.length === 0) return;
+
+ var current = this.currentFocused;
+ var currentRect = current.getBoundingClientRect();
+ var cx = currentRect.left + currentRect.width / 2;
+ var cy = currentRect.top + currentRect.height / 2;
+
+ var best = null;
+ var bestScore = Infinity;
+
+ for (var i = 0; i < focusables.length; i++) {
+ var el = focusables[i];
+ if (el === current) continue;
+ if (el.offsetParent === null) continue;
+
+ var rect = el.getBoundingClientRect();
+ var ex = rect.left + rect.width / 2;
+ var ey = rect.top + rect.height / 2;
+
+ var dx = ex - cx;
+ var dy = ey - cy;
+
+ var valid = false;
+ var primary, secondary;
+
+ switch (direction) {
+ case 'left':
+ valid = dx < -5;
+ primary = Math.abs(dx);
+ secondary = Math.abs(dy);
+ break;
+ case 'right':
+ valid = dx > 5;
+ primary = Math.abs(dx);
+ secondary = Math.abs(dy);
+ break;
+ case 'up':
+ valid = dy < -5;
+ primary = Math.abs(dy);
+ secondary = Math.abs(dx);
+ break;
+ case 'down':
+ valid = dy > 5;
+ primary = Math.abs(dy);
+ secondary = Math.abs(dx);
+ break;
+ }
+
+ if (!valid) continue;
+
+ var score = secondary * 3 + primary;
+ if (score < bestScore) {
+ bestScore = score;
+ best = el;
+ }
+ }
+
+ if (best) {
+ this.setFocus(best);
+ }
+ },
+
+ select: function() {
+ if (this.currentFocused) {
+ this.currentFocused.click();
+ }
+ }
+};
diff --git a/anixart-tizen/src/screens/details.js b/anixart-tizen/src/screens/details.js
new file mode 100644
index 0000000..e53f92c
--- /dev/null
+++ b/anixart-tizen/src/screens/details.js
@@ -0,0 +1,254 @@
+var DetailsScreen = {
+ currentRelease: null,
+
+ render: function(params) {
+ var container = document.getElementById('app');
+ container.innerHTML = '';
+ container.className = 'screen-details';
+
+ var loading = document.createElement('div');
+ loading.className = 'details-loading';
+ loading.id = 'details-loading';
+
+ var spinner = document.createElement('div');
+ spinner.className = 'spinner';
+ loading.appendChild(spinner);
+ container.appendChild(loading);
+
+ this.loadRelease(params.releaseId);
+ },
+
+ loadRelease: function(releaseId) {
+ var token = Storage.getToken();
+ var self = this;
+
+ ReleaseApi.getRelease(releaseId, token).then(function(response) {
+ var release = response.release || response;
+ self.currentRelease = release;
+ self.renderRelease(release);
+ }).catch(function(err) {
+ var container = document.getElementById('app');
+ container.innerHTML = 'Ошибка загрузки
';
+ });
+ },
+
+ renderRelease: function(release) {
+ var container = document.getElementById('app');
+ container.innerHTML = '';
+ container.className = 'screen-details';
+
+ var bgPoster = document.createElement('div');
+ bgPoster.className = 'details-bg-poster';
+ if (release.image || release.poster) {
+ bgPoster.style.backgroundImage = 'url(' + (release.image || release.poster) + ')';
+ }
+ container.appendChild(bgPoster);
+
+ var gradient = document.createElement('div');
+ gradient.className = 'details-gradient';
+ container.appendChild(gradient);
+
+ var scroll = document.createElement('div');
+ scroll.className = 'details-scroll';
+ scroll.id = 'main-scroll';
+
+ var hero = document.createElement('div');
+ hero.className = 'details-hero';
+
+ var posterCard = document.createElement('div');
+ posterCard.className = 'details-poster-card';
+ var posterImg = document.createElement('img');
+ posterImg.className = 'details-poster';
+ posterImg.src = release.image || release.poster || '';
+ posterImg.alt = release.title_ru || release.title || '';
+ posterCard.appendChild(posterImg);
+ hero.appendChild(posterCard);
+
+ var titleBlock = document.createElement('div');
+ titleBlock.className = 'details-title-block';
+
+ var title = document.createElement('h1');
+ title.className = 'details-title';
+ title.textContent = release.title_ru || release.title || '';
+ titleBlock.appendChild(title);
+
+ if (release.title_en || release.title_original) {
+ var enTitle = document.createElement('div');
+ enTitle.className = 'details-title-en';
+ enTitle.textContent = release.title_en || release.title_original || '';
+ titleBlock.appendChild(enTitle);
+ }
+
+ var badges = document.createElement('div');
+ badges.className = 'details-badges';
+
+ if (release.year) {
+ var yearBadge = document.createElement('span');
+ yearBadge.className = 'badge';
+ yearBadge.textContent = release.year;
+ badges.appendChild(yearBadge);
+ }
+
+ if (release.age_rating) {
+ var ageBadge = document.createElement('span');
+ ageBadge.className = 'badge badge-age';
+ ageBadge.textContent = release.age_rating + '+';
+ badges.appendChild(ageBadge);
+ }
+
+ if (release.status) {
+ var statuses = { 1: 'Онгоинг', 2: 'Вышел', 3: 'Анонс' };
+ if (statuses[release.status]) {
+ var statusBadge = document.createElement('span');
+ statusBadge.className = 'badge';
+ statusBadge.textContent = statuses[release.status];
+ badges.appendChild(statusBadge);
+ }
+ }
+
+ titleBlock.appendChild(badges);
+ hero.appendChild(titleBlock);
+ scroll.appendChild(hero);
+
+ var actions = document.createElement('div');
+ actions.className = 'details-actions';
+
+ var watchBtn = document.createElement('button');
+ watchBtn.className = 'details-watch-btn';
+ watchBtn.setAttribute('data-focusable', 'true');
+ watchBtn.innerHTML = ' Смотреть';
+ watchBtn.addEventListener('click', function() {
+ DetailsScreen.loadEpisodes(release.id);
+ });
+ actions.appendChild(watchBtn);
+
+ var favBtn = document.createElement('button');
+ favBtn.className = 'details-action-btn';
+ favBtn.setAttribute('data-focusable', 'true');
+ favBtn.innerHTML = 'Закладка';
+ actions.appendChild(favBtn);
+
+ var shareBtn = document.createElement('button');
+ shareBtn.className = 'details-action-btn';
+ shareBtn.setAttribute('data-focusable', 'true');
+ shareBtn.innerHTML = 'Поделиться';
+ actions.appendChild(shareBtn);
+
+ scroll.appendChild(actions);
+
+ if (release.description) {
+ var descSection = document.createElement('div');
+ descSection.className = 'details-section';
+ var descTitle = document.createElement('div');
+ descTitle.className = 'details-section-title';
+ descTitle.textContent = 'Описание';
+ descSection.appendChild(descTitle);
+ var descText = document.createElement('div');
+ descText.className = 'details-description';
+ descText.textContent = release.description;
+ descSection.appendChild(descText);
+ scroll.appendChild(descSection);
+ }
+
+ if (release.genres && release.genres.length > 0) {
+ var genresSection = document.createElement('div');
+ genresSection.className = 'details-section';
+ var genresTitle = document.createElement('div');
+ genresTitle.className = 'details-section-title';
+ genresTitle.textContent = 'Жанры';
+ genresSection.appendChild(genresTitle);
+ var genresList = document.createElement('div');
+ genresList.className = 'details-genres';
+ for (var i = 0; i < release.genres.length; i++) {
+ var chip = document.createElement('span');
+ chip.className = 'genre-chip';
+ chip.textContent = release.genres[i].name || release.genres[i];
+ genresList.appendChild(chip);
+ }
+ genresSection.appendChild(genresList);
+ scroll.appendChild(genresSection);
+ }
+
+ var episodesSection = document.createElement('div');
+ episodesSection.className = 'details-section';
+ episodesSection.id = 'episodes-section';
+ var epTitle = document.createElement('div');
+ epTitle.className = 'details-section-title';
+ epTitle.textContent = 'Эпизоды';
+ episodesSection.appendChild(epTitle);
+ var epContainer = document.createElement('div');
+ epContainer.id = 'episodes-container';
+ epContainer.className = 'episodes-container';
+ var epLoading = document.createElement('div');
+ epLoading.className = 'episodes-loading';
+ epLoading.textContent = 'Загрузка...';
+ epContainer.appendChild(epLoading);
+ episodesSection.appendChild(epContainer);
+ scroll.appendChild(episodesSection);
+
+ container.appendChild(scroll);
+
+ this.loadEpisodesList(release.id);
+
+ setTimeout(function() {
+ FocusManager.setFocus(watchBtn);
+ }, 200);
+ },
+
+ loadEpisodesList: function(releaseId) {
+ var token = Storage.getToken();
+ var container = document.getElementById('episodes-container');
+
+ ReleaseApi.getSources(releaseId, token).then(function(response) {
+ var sources = response.content || response || [];
+ if (sources.length > 0) {
+ return ReleaseApi.getEpisodes(releaseId, sources[0].id, token);
+ }
+ return ReleaseApi.getEpisodes(releaseId, null, token);
+ }).then(function(response) {
+ var episodes = response.content || response || [];
+ if (!container) return;
+ container.innerHTML = '';
+
+ if (episodes.length === 0) {
+ container.innerHTML = 'Нет доступных эпизодов
';
+ return;
+ }
+
+ var list = document.createElement('div');
+ list.className = 'episodes-list section-scroll';
+
+ for (var i = 0; i < episodes.length; i++) {
+ var ep = episodes[i];
+ var epCard = document.createElement('div');
+ epCard.className = 'episode-card';
+ epCard.setAttribute('data-focusable', 'true');
+
+ var epNum = document.createElement('div');
+ epNum.className = 'episode-number';
+ epNum.textContent = (ep.position != null ? ep.position : (i + 1));
+ epCard.appendChild(epNum);
+
+ var epName = document.createElement('div');
+ epName.className = 'episode-name';
+ epName.textContent = ep.name || ('Эпизод ' + (ep.position != null ? ep.position : (i + 1)));
+ epCard.appendChild(epName);
+
+ list.appendChild(epCard);
+ }
+
+ container.appendChild(list);
+ }).catch(function(err) {
+ if (container) {
+ container.innerHTML = 'Ошибка загрузки эпизодов
';
+ }
+ });
+ },
+
+ loadEpisodes: function(releaseId) {
+ var section = document.getElementById('episodes-section');
+ if (section) {
+ section.scrollIntoView({ behavior: 'smooth' });
+ }
+ }
+};
diff --git a/anixart-tizen/src/screens/home.js b/anixart-tizen/src/screens/home.js
new file mode 100644
index 0000000..579edb9
--- /dev/null
+++ b/anixart-tizen/src/screens/home.js
@@ -0,0 +1,360 @@
+var HomeScreen = {
+ render: function() {
+ var container = document.getElementById('app');
+ container.innerHTML = '';
+ container.className = 'screen-home';
+
+ var toolbar = this.createToolbar();
+ container.appendChild(toolbar);
+
+ var mainScroll = document.createElement('div');
+ mainScroll.id = 'main-scroll';
+ mainScroll.className = 'main-scroll';
+
+ var content = document.createElement('div');
+ content.className = 'home-content';
+ content.id = 'home-content';
+
+ var skeleton = this.createSkeleton();
+ content.appendChild(skeleton);
+
+ mainScroll.appendChild(content);
+ container.appendChild(mainScroll);
+
+ var bottomNav = BottomNav.render('home');
+ container.appendChild(bottomNav);
+
+ this.loadData();
+ },
+
+ createToolbar: function() {
+ var toolbar = document.createElement('div');
+ toolbar.className = 'toolbar';
+
+ var searchBar = document.createElement('div');
+ searchBar.className = 'search-bar';
+ searchBar.setAttribute('data-focusable', 'true');
+
+ var searchIcon = document.createElement('span');
+ searchIcon.className = 'search-icon';
+ searchIcon.innerHTML = '';
+ searchBar.appendChild(searchIcon);
+
+ var searchText = document.createElement('span');
+ searchText.className = 'search-text';
+ searchText.textContent = 'Поиск аниме';
+ searchBar.appendChild(searchText);
+
+ toolbar.appendChild(searchBar);
+
+ var actions = document.createElement('div');
+ actions.className = 'toolbar-actions';
+
+ var settingsBtn = document.createElement('button');
+ settingsBtn.className = 'toolbar-btn';
+ settingsBtn.setAttribute('data-focusable', 'true');
+ settingsBtn.innerHTML = '';
+ actions.appendChild(settingsBtn);
+
+ var notifBtn = document.createElement('button');
+ notifBtn.className = 'toolbar-btn';
+ notifBtn.setAttribute('data-focusable', 'true');
+ notifBtn.innerHTML = '';
+ actions.appendChild(notifBtn);
+
+ toolbar.appendChild(actions);
+ return toolbar;
+ },
+
+ createSkeleton: function() {
+ var skeleton = document.createElement('div');
+ skeleton.className = 'skeleton-container';
+ skeleton.id = 'skeleton';
+
+ for (var s = 0; s < 3; s++) {
+ var section = document.createElement('div');
+ section.className = 'skeleton-section';
+
+ var header = document.createElement('div');
+ header.className = 'skeleton-header shimmer';
+ section.appendChild(header);
+
+ var row = document.createElement('div');
+ row.className = 'skeleton-row';
+ for (var i = 0; i < 6; i++) {
+ var card = document.createElement('div');
+ card.className = 'skeleton-card';
+ var poster = document.createElement('div');
+ poster.className = 'skeleton-poster shimmer';
+ card.appendChild(poster);
+ var line = document.createElement('div');
+ line.className = 'skeleton-line shimmer';
+ card.appendChild(line);
+ row.appendChild(card);
+ }
+ section.appendChild(row);
+ skeleton.appendChild(section);
+ }
+ return skeleton;
+ },
+
+ loadData: function() {
+ var token = Storage.getToken();
+ var content = document.getElementById('home-content');
+ var skeleton = document.getElementById('skeleton');
+
+ var promises = [
+ DiscoverApi.getInteresting().catch(function() { return null; })
+ ];
+
+ if (token) {
+ promises.push(
+ DiscoverApi.getRecommendations(0, 0, token).catch(function() { return null; }),
+ DiscoverApi.getWatching(0, token).catch(function() { return null; }),
+ DiscoverApi.getDiscussing(token).catch(function() { return null; })
+ );
+ }
+
+ Promise.all(promises).then(function(results) {
+ if (skeleton) skeleton.remove();
+
+ var interesting = results[0];
+ if (interesting && interesting.content && interesting.content.length > 0) {
+ var interestingSection = HomeScreen.createInterestingSection(interesting.content);
+ content.appendChild(interestingSection);
+ }
+
+ if (token) {
+ var recommendations = results[1];
+ if (recommendations && recommendations.content && recommendations.content.length > 0) {
+ var recSection = HomeScreen.createReleaseSection(
+ 'Рекомендации',
+ 'На основе ваших оценок',
+ recommendations.content,
+ true
+ );
+ content.appendChild(recSection);
+ }
+
+ var watching = results[2];
+ if (watching && watching.content && watching.content.length > 0) {
+ var watchSection = HomeScreen.createReleaseSection(
+ 'Смотрят сейчас',
+ null,
+ watching.content,
+ true
+ );
+ content.appendChild(watchSection);
+ }
+
+ var discussing = results[3];
+ if (discussing && discussing.content && discussing.content.length > 0) {
+ var discSection = HomeScreen.createReleaseSection(
+ 'Обсуждаемое сегодня',
+ null,
+ discussing.content,
+ false
+ );
+ content.appendChild(discSection);
+ }
+ }
+
+ setTimeout(function() {
+ FocusManager.focusFirst(content);
+ }, 100);
+
+ }).catch(function(err) {
+ if (skeleton) skeleton.remove();
+ var errorEl = document.createElement('div');
+ errorEl.className = 'error-state';
+ errorEl.textContent = 'Ошибка загрузки. Нажмите OK для повтора.';
+ errorEl.setAttribute('data-focusable', 'true');
+ errorEl.addEventListener('click', function() {
+ content.innerHTML = '';
+ var newSkeleton = HomeScreen.createSkeleton();
+ content.appendChild(newSkeleton);
+ HomeScreen.loadData();
+ });
+ content.appendChild(errorEl);
+ });
+ },
+
+ createInterestingSection: function(items) {
+ var section = document.createElement('div');
+ section.className = 'home-section interesting-section';
+
+ var scroll = document.createElement('div');
+ scroll.className = 'section-scroll interesting-scroll';
+
+ for (var i = 0; i < items.length; i++) {
+ var item = items[i];
+ var card = this.createInterestingCard(item);
+ scroll.appendChild(card);
+ }
+
+ section.appendChild(scroll);
+ return section;
+ },
+
+ createInterestingCard: function(item) {
+ var release = item.release || item;
+ var card = document.createElement('div');
+ card.className = 'interesting-card';
+ card.setAttribute('data-focusable', 'true');
+ card.setAttribute('data-release-id', release.id || '');
+
+ var img = document.createElement('img');
+ img.className = 'interesting-poster';
+ img.alt = release.title_ru || release.title || '';
+ img.loading = 'lazy';
+ if (release.image) {
+ img.src = release.image;
+ } else if (release.poster) {
+ img.src = release.poster;
+ }
+ img.onerror = function() { this.style.display = 'none'; };
+ card.appendChild(img);
+
+ var overlay = document.createElement('div');
+ overlay.className = 'interesting-overlay';
+
+ var title = document.createElement('div');
+ title.className = 'interesting-title';
+ title.textContent = release.title_ru || release.title || '';
+ overlay.appendChild(title);
+
+ card.appendChild(overlay);
+
+ card.addEventListener('click', function() {
+ App.showScreen('details', { releaseId: release.id });
+ });
+
+ return card;
+ },
+
+ createReleaseSection: function(titleText, subtitleText, items, showMore) {
+ var section = document.createElement('div');
+ section.className = 'home-section';
+
+ var header = document.createElement('div');
+ header.className = 'section-header';
+
+ var headerLeft = document.createElement('div');
+ headerLeft.className = 'section-header-left';
+
+ var title = document.createElement('span');
+ title.className = 'section-title';
+ title.textContent = titleText;
+ headerLeft.appendChild(title);
+
+ if (subtitleText) {
+ var subtitle = document.createElement('span');
+ subtitle.className = 'section-subtitle';
+ subtitle.textContent = subtitleText;
+ headerLeft.appendChild(subtitle);
+ }
+
+ header.appendChild(headerLeft);
+
+ if (showMore) {
+ var moreBtn = document.createElement('span');
+ moreBtn.className = 'section-show-more';
+ moreBtn.textContent = 'Показать все';
+ moreBtn.setAttribute('data-focusable', 'true');
+ header.appendChild(moreBtn);
+ }
+
+ section.appendChild(header);
+
+ var scroll = document.createElement('div');
+ scroll.className = 'section-scroll';
+
+ for (var i = 0; i < items.length; i++) {
+ var card = this.createReleaseCard(items[i]);
+ scroll.appendChild(card);
+ }
+
+ section.appendChild(scroll);
+
+ var separator = document.createElement('div');
+ separator.className = 'section-separator';
+ section.appendChild(separator);
+
+ return section;
+ },
+
+ createReleaseCard: function(release) {
+ var card = document.createElement('div');
+ card.className = 'release-card';
+ card.setAttribute('data-focusable', 'true');
+ card.setAttribute('data-release-id', release.id || '');
+
+ var posterWrap = document.createElement('div');
+ posterWrap.className = 'release-poster-wrap';
+
+ var img = document.createElement('img');
+ img.className = 'release-poster';
+ img.alt = release.title_ru || release.title || '';
+ img.loading = 'lazy';
+ if (release.image) {
+ img.src = release.image;
+ } else if (release.poster) {
+ img.src = release.poster;
+ }
+ img.onerror = function() {
+ this.style.background = 'var(--color-surface)';
+ };
+ posterWrap.appendChild(img);
+
+ if (release.status) {
+ var statusBubble = document.createElement('div');
+ statusBubble.className = 'release-status';
+ var statusTexts = { 1: 'Онгоинг', 2: 'Вышел', 3: 'Анонс' };
+ statusBubble.textContent = statusTexts[release.status] || '';
+ if (statusBubble.textContent) {
+ posterWrap.appendChild(statusBubble);
+ }
+ }
+
+ card.appendChild(posterWrap);
+
+ var info = document.createElement('div');
+ info.className = 'release-info';
+
+ var title = document.createElement('div');
+ title.className = 'release-title';
+ title.textContent = release.title_ru || release.title || '';
+ info.appendChild(title);
+
+ var meta = document.createElement('div');
+ meta.className = 'release-meta';
+
+ if (release.episodes_total || release.episodesCount) {
+ var eps = document.createElement('span');
+ eps.textContent = (release.episodes_total || release.episodesCount || '?') + ' эп.';
+ meta.appendChild(eps);
+ }
+
+ if (release.grade) {
+ if (meta.childNodes.length > 0) {
+ var dot = document.createElement('span');
+ dot.className = 'meta-dot';
+ dot.textContent = ' · ';
+ meta.appendChild(dot);
+ }
+ var grade = document.createElement('span');
+ grade.className = 'release-grade';
+ grade.textContent = parseFloat(release.grade).toFixed(1);
+ meta.appendChild(grade);
+ }
+
+ info.appendChild(meta);
+ card.appendChild(info);
+
+ card.addEventListener('click', function() {
+ App.showScreen('details', { releaseId: release.id });
+ });
+
+ return card;
+ }
+};
diff --git a/anixart-tizen/src/screens/login.js b/anixart-tizen/src/screens/login.js
new file mode 100644
index 0000000..d853597
--- /dev/null
+++ b/anixart-tizen/src/screens/login.js
@@ -0,0 +1,127 @@
+var LoginScreen = {
+ render: function() {
+ var container = document.getElementById('app');
+ container.innerHTML = '';
+ container.className = 'screen-login';
+
+ var wrapper = document.createElement('div');
+ wrapper.className = 'login-wrapper';
+
+ var logo = document.createElement('div');
+ logo.className = 'login-logo';
+ logo.innerHTML = '
';
+ wrapper.appendChild(logo);
+
+ var title = document.createElement('h1');
+ title.className = 'login-title';
+ title.textContent = 'С возвращением';
+ wrapper.appendChild(title);
+
+ var subtitle = document.createElement('p');
+ subtitle.className = 'login-subtitle';
+ subtitle.textContent = 'Войдите, чтобы продолжить';
+ wrapper.appendChild(subtitle);
+
+ var form = document.createElement('div');
+ form.className = 'login-form';
+
+ var loginGroup = document.createElement('div');
+ loginGroup.className = 'input-group';
+ var loginInput = document.createElement('input');
+ loginInput.type = 'text';
+ loginInput.id = 'login-input';
+ loginInput.className = 'login-field';
+ loginInput.placeholder = 'Почта или никнейм';
+ loginInput.setAttribute('data-focusable', 'true');
+ loginInput.addEventListener('focus', function() {
+ FocusManager.setFocus(loginInput);
+ });
+ loginGroup.appendChild(loginInput);
+ form.appendChild(loginGroup);
+
+ var passGroup = document.createElement('div');
+ passGroup.className = 'input-group';
+ var passInput = document.createElement('input');
+ passInput.type = 'password';
+ passInput.id = 'password-input';
+ passInput.className = 'login-field';
+ passInput.placeholder = 'Пароль';
+ passInput.setAttribute('data-focusable', 'true');
+ passInput.addEventListener('focus', function() {
+ FocusManager.setFocus(passInput);
+ });
+ passGroup.appendChild(passInput);
+
+ var togglePass = document.createElement('button');
+ togglePass.className = 'password-toggle';
+ togglePass.setAttribute('data-focusable', 'true');
+ togglePass.innerHTML = '';
+ togglePass.addEventListener('click', function() {
+ passInput.type = passInput.type === 'password' ? 'text' : 'password';
+ });
+ passGroup.appendChild(togglePass);
+ form.appendChild(passGroup);
+
+ var errorMsg = document.createElement('div');
+ errorMsg.className = 'login-error';
+ errorMsg.id = 'login-error';
+ form.appendChild(errorMsg);
+
+ var loginBtn = document.createElement('button');
+ loginBtn.className = 'login-button';
+ loginBtn.id = 'login-button';
+ loginBtn.textContent = 'Войти';
+ loginBtn.setAttribute('data-focusable', 'true');
+ loginBtn.addEventListener('click', function() {
+ LoginScreen.doLogin();
+ });
+ form.appendChild(loginBtn);
+
+ wrapper.appendChild(form);
+ container.appendChild(wrapper);
+
+ setTimeout(function() {
+ FocusManager.setFocus(loginInput);
+ }, 100);
+ },
+
+ doLogin: function() {
+ var login = document.getElementById('login-input').value.trim();
+ var password = document.getElementById('password-input').value;
+ var errorEl = document.getElementById('login-error');
+ var button = document.getElementById('login-button');
+
+ if (!login || !password) {
+ errorEl.textContent = 'Введите логин и пароль';
+ errorEl.style.display = 'block';
+ return;
+ }
+
+ errorEl.style.display = 'none';
+ button.textContent = 'Вход...';
+ button.disabled = true;
+
+ AuthApi.signIn(login, password).then(function(response) {
+ button.textContent = 'Войти';
+ button.disabled = false;
+
+ if (response.status && response.status !== 0) {
+ errorEl.textContent = AuthApi.getErrorMessage(response.status);
+ errorEl.style.display = 'block';
+ return;
+ }
+
+ if (response.profileToken && response.profileToken.token) {
+ App.showScreen('home');
+ } else {
+ errorEl.textContent = 'Ошибка авторизации';
+ errorEl.style.display = 'block';
+ }
+ }).catch(function(err) {
+ button.textContent = 'Войти';
+ button.disabled = false;
+ errorEl.textContent = 'Ошибка сети. Проверьте подключение.';
+ errorEl.style.display = 'block';
+ });
+ }
+};
diff --git a/anixart-tizen/src/services/storage.js b/anixart-tizen/src/services/storage.js
new file mode 100644
index 0000000..0fa6b69
--- /dev/null
+++ b/anixart-tizen/src/services/storage.js
@@ -0,0 +1,50 @@
+var Storage = {
+ TOKEN_KEY: 'anixart_token',
+ TOKEN_ID_KEY: 'anixart_token_id',
+ PROFILE_KEY: 'anixart_profile',
+ THEME_KEY: 'anixart_theme',
+
+ setToken: function(token) {
+ localStorage.setItem(this.TOKEN_KEY, token);
+ },
+
+ getToken: function() {
+ return localStorage.getItem(this.TOKEN_KEY);
+ },
+
+ setTokenId: function(id) {
+ localStorage.setItem(this.TOKEN_ID_KEY, String(id));
+ },
+
+ getTokenId: function() {
+ var val = localStorage.getItem(this.TOKEN_ID_KEY);
+ return val ? parseInt(val, 10) : null;
+ },
+
+ setProfile: function(profile) {
+ localStorage.setItem(this.PROFILE_KEY, JSON.stringify(profile));
+ },
+
+ getProfile: function() {
+ var val = localStorage.getItem(this.PROFILE_KEY);
+ return val ? JSON.parse(val) : null;
+ },
+
+ getTheme: function() {
+ return localStorage.getItem(this.THEME_KEY) || 'dark';
+ },
+
+ setTheme: function(theme) {
+ localStorage.setItem(this.THEME_KEY, theme);
+ },
+
+ isLoggedIn: function() {
+ return !!this.getToken();
+ },
+
+ clearAuth: function() {
+ localStorage.removeItem(this.TOKEN_KEY);
+ localStorage.removeItem(this.TOKEN_ID_KEY);
+ localStorage.removeItem(this.PROFILE_KEY);
+ }
+};
diff --git a/anixart-tizen/styles/main.css b/anixart-tizen/styles/main.css
new file mode 100644
index 0000000..d3046fd
--- /dev/null
+++ b/anixart-tizen/styles/main.css
@@ -0,0 +1,799 @@
+*, *::before, *::after {
+ box-sizing: border-box;
+ margin: 0;
+ padding: 0;
+}
+
+html, body {
+ width: 100%;
+ height: 100%;
+ overflow: hidden;
+ background: var(--color-screen-bg);
+ color: var(--color-primary-text);
+ font-family: var(--font-body);
+ font-size: 16px;
+ -webkit-font-smoothing: antialiased;
+}
+
+#app {
+ width: 100%;
+ height: 100%;
+ position: relative;
+ overflow: hidden;
+}
+
+img {
+ display: block;
+}
+
+button {
+ background: none;
+ border: none;
+ color: inherit;
+ cursor: pointer;
+ font-family: inherit;
+ outline: none;
+}
+
+input {
+ font-family: inherit;
+ outline: none;
+}
+
+.focused {
+ outline: var(--focus-ring-width) solid var(--focus-ring-color) !important;
+ outline-offset: 2px;
+ transform: scale(var(--focus-scale));
+ transition: transform var(--focus-transition), outline var(--focus-transition);
+ z-index: 10;
+}
+
+@keyframes shimmer {
+ 0% { background-position: -200% 0; }
+ 100% { background-position: 200% 0; }
+}
+
+.shimmer {
+ background: linear-gradient(90deg,
+ var(--color-shimmer-base) 25%,
+ var(--color-shimmer-highlight) 50%,
+ var(--color-shimmer-base) 75%
+ );
+ background-size: 200% 100%;
+ animation: shimmer 1.5s infinite;
+}
+
+/* ===== LOGIN SCREEN ===== */
+
+.screen-login {
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ background: var(--color-screen-bg);
+}
+
+.login-wrapper {
+ width: 420px;
+ max-width: 90%;
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+}
+
+.login-logo {
+ margin-bottom: 48px;
+}
+
+.login-logo img {
+ width: 120px;
+ height: auto;
+}
+
+.login-title {
+ font-family: var(--font-display);
+ font-size: 28px;
+ font-weight: 700;
+ color: var(--color-primary-text);
+ margin-bottom: 8px;
+ text-align: center;
+}
+
+.login-subtitle {
+ font-size: 16px;
+ color: var(--color-secondary-text);
+ margin-bottom: 40px;
+ text-align: center;
+}
+
+.login-form {
+ width: 100%;
+ display: flex;
+ flex-direction: column;
+ gap: 16px;
+}
+
+.input-group {
+ position: relative;
+ width: 100%;
+}
+
+.login-field {
+ width: 100%;
+ height: 56px;
+ padding: 0 16px;
+ background: transparent;
+ border: 2px solid var(--color-border);
+ border-radius: 12px;
+ color: var(--color-primary-text);
+ font-size: 16px;
+ transition: border-color 0.2s;
+}
+
+.login-field::placeholder {
+ color: var(--color-hint-text);
+}
+
+.login-field:focus,
+.login-field.focused {
+ border-color: var(--color-accent);
+}
+
+.login-field.focused {
+ outline: none !important;
+ transform: none;
+}
+
+.password-toggle {
+ position: absolute;
+ right: 12px;
+ top: 50%;
+ transform: translateY(-50%);
+ width: 40px;
+ height: 40px;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ color: var(--color-icon-alt-tint);
+ border-radius: 50%;
+}
+
+.login-error {
+ display: none;
+ color: var(--color-red);
+ font-size: 14px;
+ text-align: center;
+ padding: 8px;
+}
+
+.login-button {
+ width: 100%;
+ height: 48px;
+ background: var(--color-button-primary);
+ color: var(--color-button-primary-text);
+ border: none;
+ border-radius: 12px;
+ font-family: var(--font-medium);
+ font-size: 16px;
+ font-weight: 500;
+ margin-top: 8px;
+ transition: opacity 0.2s;
+}
+
+.login-button:disabled {
+ opacity: 0.6;
+}
+
+.login-button.focused {
+ transform: scale(1.02);
+}
+
+/* ===== TOOLBAR ===== */
+
+.toolbar {
+ height: var(--toolbar-height);
+ display: flex;
+ align-items: center;
+ padding: 0 var(--section-padding-horizontal);
+ gap: 12px;
+ background: var(--color-screen-bg);
+ position: relative;
+ z-index: 20;
+}
+
+.search-bar {
+ flex: 1;
+ height: var(--search-bar-height);
+ background: var(--color-search-bar-bg);
+ border-radius: 24px;
+ display: flex;
+ align-items: center;
+ padding: 0 16px;
+ gap: 12px;
+}
+
+.search-icon {
+ color: var(--color-icon-tint);
+ display: flex;
+ align-items: center;
+ flex-shrink: 0;
+}
+
+.search-text {
+ color: var(--color-hint-text);
+ font-size: 16px;
+}
+
+.toolbar-actions {
+ display: flex;
+ gap: 4px;
+}
+
+.toolbar-btn {
+ width: 48px;
+ height: 48px;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ color: var(--color-icon-tint);
+ border-radius: 50%;
+}
+
+/* ===== MAIN SCROLL ===== */
+
+.main-scroll {
+ position: absolute;
+ top: var(--toolbar-height);
+ left: 0;
+ right: 0;
+ bottom: var(--bottom-nav-height);
+ overflow-y: auto;
+ overflow-x: hidden;
+ scroll-behavior: smooth;
+ -webkit-overflow-scrolling: touch;
+}
+
+.main-scroll::-webkit-scrollbar {
+ display: none;
+}
+
+.home-content {
+ padding-bottom: 24px;
+}
+
+/* ===== SECTIONS ===== */
+
+.home-section {
+ margin-bottom: 8px;
+}
+
+.section-header {
+ height: var(--section-header-height);
+ display: flex;
+ align-items: center;
+ justify-content: space-between;
+ padding: 0 var(--section-padding-horizontal);
+}
+
+.section-header-left {
+ display: flex;
+ align-items: baseline;
+ gap: 8px;
+}
+
+.section-title {
+ font-family: var(--font-medium);
+ font-size: var(--section-header-font-size);
+ color: var(--color-primary-text);
+ font-weight: 500;
+}
+
+.section-subtitle {
+ font-size: 14px;
+ color: var(--color-secondary-text);
+}
+
+.section-show-more {
+ font-family: var(--font-medium);
+ font-size: 14px;
+ color: var(--color-accent);
+ cursor: pointer;
+}
+
+.section-scroll {
+ display: flex;
+ gap: 8px;
+ overflow-x: auto;
+ overflow-y: hidden;
+ padding: 0 var(--section-padding-horizontal);
+ scroll-behavior: smooth;
+ -webkit-overflow-scrolling: touch;
+ scroll-snap-type: x proximity;
+}
+
+.section-scroll::-webkit-scrollbar {
+ display: none;
+}
+
+.section-separator {
+ height: 1px;
+ margin: 16px var(--section-padding-horizontal) 0;
+ background: var(--color-separator);
+}
+
+/* ===== INTERESTING SECTION ===== */
+
+.interesting-scroll {
+ gap: 12px;
+}
+
+.interesting-card {
+ flex-shrink: 0;
+ width: 320px;
+ height: 180px;
+ border-radius: 16px;
+ overflow: hidden;
+ position: relative;
+ background: var(--color-surface);
+ scroll-snap-align: start;
+}
+
+.interesting-poster {
+ width: 100%;
+ height: 100%;
+ object-fit: cover;
+}
+
+.interesting-overlay {
+ position: absolute;
+ bottom: 0;
+ left: 0;
+ right: 0;
+ padding: 12px 16px;
+ background: linear-gradient(transparent, rgba(0,0,0,0.7));
+}
+
+.interesting-title {
+ color: #ffffff;
+ font-family: var(--font-medium);
+ font-size: 15px;
+ font-weight: 500;
+ text-shadow: 0 1px 3px rgba(0,0,0,0.5);
+ white-space: nowrap;
+ overflow: hidden;
+ text-overflow: ellipsis;
+}
+
+/* ===== RELEASE CARD ===== */
+
+.release-card {
+ flex-shrink: 0;
+ width: var(--release-card-min-width);
+ padding-top: var(--release-card-padding-top);
+ padding-bottom: var(--release-card-padding-bottom);
+ padding-left: var(--release-card-padding-start);
+ padding-right: var(--release-card-padding-end);
+ scroll-snap-align: start;
+}
+
+.release-poster-wrap {
+ position: relative;
+ width: 100%;
+ border-radius: var(--release-card-corner-radius);
+ overflow: hidden;
+ background: var(--color-surface);
+}
+
+.release-poster {
+ width: 100%;
+ height: var(--release-card-poster-height);
+ object-fit: cover;
+ display: block;
+}
+
+.release-status {
+ position: absolute;
+ bottom: 8px;
+ left: 8px;
+ background: var(--color-status-bubble);
+ color: #ffffff;
+ font-size: 11px;
+ padding: 3px 8px;
+ border-radius: 8px;
+ backdrop-filter: blur(4px);
+ -webkit-backdrop-filter: blur(4px);
+}
+
+.release-info {
+ margin-top: 8px;
+}
+
+.release-title {
+ font-family: var(--font-medium);
+ font-size: var(--release-card-title-size);
+ font-weight: 500;
+ color: var(--color-primary-text);
+ display: -webkit-box;
+ -webkit-line-clamp: 2;
+ -webkit-box-orient: vertical;
+ overflow: hidden;
+ line-height: 1.3;
+}
+
+.release-meta {
+ display: flex;
+ align-items: center;
+ gap: 2px;
+ margin-top: 4px;
+ font-size: var(--release-card-text-size);
+ color: var(--color-secondary-text);
+}
+
+.meta-dot {
+ color: var(--color-tertiary-text);
+}
+
+.release-grade {
+ color: var(--color-secondary-text);
+}
+
+/* ===== SKELETON ===== */
+
+.skeleton-container {
+ padding: 0 var(--section-padding-horizontal);
+}
+
+.skeleton-section {
+ margin-bottom: 24px;
+}
+
+.skeleton-header {
+ width: 160px;
+ height: 24px;
+ border-radius: 8px;
+ margin: 16px 0;
+}
+
+.skeleton-row {
+ display: flex;
+ gap: 8px;
+ overflow: hidden;
+}
+
+.skeleton-card {
+ flex-shrink: 0;
+ width: var(--release-card-min-width);
+}
+
+.skeleton-poster {
+ width: 100%;
+ height: var(--release-card-poster-height);
+ border-radius: var(--release-card-corner-radius);
+}
+
+.skeleton-line {
+ width: 80%;
+ height: 14px;
+ border-radius: 4px;
+ margin-top: 10px;
+}
+
+/* ===== BOTTOM NAV ===== */
+
+.bottom-nav {
+ position: absolute;
+ bottom: 0;
+ left: 0;
+ right: 0;
+ height: var(--bottom-nav-height);
+ background: var(--color-bottom-nav-bg);
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ gap: 8px;
+ z-index: 30;
+ border-top: 1px solid var(--color-separator);
+}
+
+.nav-tab {
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+ justify-content: center;
+ width: 80px;
+ height: 100%;
+ gap: 4px;
+ padding: 0;
+}
+
+.nav-indicator {
+ width: 56px;
+ height: 28px;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ border-radius: 14px;
+ transition: background 0.2s;
+}
+
+.nav-tab.active .nav-indicator {
+ background: var(--color-bottom-nav-indicator);
+}
+
+.nav-icon {
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ color: var(--color-bottom-nav-icon);
+}
+
+.nav-tab.active .nav-icon {
+ color: var(--color-bottom-nav-icon-active);
+}
+
+.nav-label {
+ font-size: 12px;
+ font-family: var(--font-medium);
+ color: var(--color-bottom-nav-label);
+}
+
+.nav-tab.active .nav-label {
+ color: var(--color-bottom-nav-label-active);
+}
+
+/* ===== DETAILS SCREEN ===== */
+
+.screen-details {
+ background: var(--color-screen-bg);
+}
+
+.details-loading {
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ width: 100%;
+ height: 100%;
+}
+
+.spinner {
+ width: 40px;
+ height: 40px;
+ border: 3px solid var(--color-surface);
+ border-top-color: var(--color-accent);
+ border-radius: 50%;
+ animation: spin 0.8s linear infinite;
+}
+
+@keyframes spin {
+ to { transform: rotate(360deg); }
+}
+
+.details-bg-poster {
+ position: absolute;
+ top: 0;
+ left: 0;
+ right: 0;
+ height: 400px;
+ background-size: cover;
+ background-position: center top;
+ filter: blur(20px) brightness(0.5);
+ transform: scale(1.1);
+ z-index: 0;
+}
+
+.details-gradient {
+ position: absolute;
+ top: 0;
+ left: 0;
+ right: 0;
+ height: 450px;
+ background: linear-gradient(to bottom, transparent 0%, var(--color-screen-bg) 100%);
+ z-index: 1;
+}
+
+.details-scroll {
+ position: absolute;
+ top: 0;
+ left: 0;
+ right: 0;
+ bottom: 0;
+ overflow-y: auto;
+ overflow-x: hidden;
+ z-index: 2;
+ padding: 40px 60px;
+}
+
+.details-scroll::-webkit-scrollbar {
+ display: none;
+}
+
+.details-hero {
+ display: flex;
+ gap: 40px;
+ align-items: flex-start;
+ margin-bottom: 32px;
+}
+
+.details-poster-card {
+ flex-shrink: 0;
+ width: 230px;
+ border-radius: 24px;
+ overflow: hidden;
+ box-shadow: 0 8px 32px rgba(0,0,0,0.3);
+}
+
+.details-poster {
+ width: 100%;
+ height: 345px;
+ object-fit: cover;
+ display: block;
+}
+
+.details-title-block {
+ padding-top: 60px;
+}
+
+.details-title {
+ font-family: var(--font-display);
+ font-size: 28px;
+ font-weight: 700;
+ color: var(--color-primary-text);
+ margin-bottom: 8px;
+ line-height: 1.2;
+}
+
+.details-title-en {
+ font-size: 16px;
+ color: var(--color-secondary-text);
+ margin-bottom: 16px;
+}
+
+.details-badges {
+ display: flex;
+ gap: 8px;
+ flex-wrap: wrap;
+}
+
+.badge {
+ background: var(--color-surface);
+ color: var(--color-secondary-text);
+ font-size: 13px;
+ padding: 4px 12px;
+ border-radius: 8px;
+}
+
+.badge-age {
+ background: var(--color-accent-alpha-10);
+ color: var(--color-accent);
+}
+
+.details-actions {
+ display: flex;
+ gap: 16px;
+ margin-bottom: 32px;
+ align-items: center;
+}
+
+.details-watch-btn {
+ display: flex;
+ align-items: center;
+ gap: 8px;
+ height: 48px;
+ padding: 0 32px;
+ background: var(--color-button-primary);
+ color: var(--color-button-primary-text);
+ border: none;
+ border-radius: 24px;
+ font-family: var(--font-medium);
+ font-size: 16px;
+ font-weight: 500;
+}
+
+.details-action-btn {
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+ gap: 4px;
+ color: var(--color-secondary-text);
+ font-size: 12px;
+ padding: 8px 16px;
+ border-radius: 12px;
+}
+
+.details-action-btn svg {
+ flex-shrink: 0;
+}
+
+.details-section {
+ margin-bottom: 24px;
+}
+
+.details-section-title {
+ font-family: var(--font-medium);
+ font-size: 18px;
+ font-weight: 500;
+ color: var(--color-primary-text);
+ margin-bottom: 12px;
+}
+
+.details-description {
+ font-size: 15px;
+ line-height: 1.6;
+ color: var(--color-secondary-text);
+ max-width: 800px;
+}
+
+.details-genres {
+ display: flex;
+ gap: 8px;
+ flex-wrap: wrap;
+}
+
+.genre-chip {
+ background: var(--color-surface);
+ color: var(--color-secondary-text);
+ font-size: 14px;
+ padding: 6px 16px;
+ border-radius: 16px;
+}
+
+/* ===== EPISODES ===== */
+
+.episodes-container {
+ min-height: 60px;
+}
+
+.episodes-loading,
+.episodes-empty {
+ color: var(--color-secondary-text);
+ font-size: 14px;
+ padding: 16px 0;
+}
+
+.episodes-list {
+ display: flex;
+ gap: 8px;
+ overflow-x: auto;
+ padding-bottom: 8px;
+}
+
+.episodes-list::-webkit-scrollbar {
+ display: none;
+}
+
+.episode-card {
+ flex-shrink: 0;
+ width: 160px;
+ background: var(--color-surface);
+ border-radius: 12px;
+ padding: 16px;
+ display: flex;
+ flex-direction: column;
+ gap: 4px;
+}
+
+.episode-number {
+ font-family: var(--font-display);
+ font-size: 24px;
+ font-weight: 700;
+ color: var(--color-primary-text);
+}
+
+.episode-name {
+ font-size: 13px;
+ color: var(--color-secondary-text);
+ white-space: nowrap;
+ overflow: hidden;
+ text-overflow: ellipsis;
+}
+
+/* ===== ERROR STATE ===== */
+
+.error-state {
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ height: 200px;
+ color: var(--color-secondary-text);
+ font-size: 16px;
+ text-align: center;
+}
diff --git a/anixart-tizen/styles/theme.css b/anixart-tizen/styles/theme.css
new file mode 100644
index 0000000..34728d4
--- /dev/null
+++ b/anixart-tizen/styles/theme.css
@@ -0,0 +1,162 @@
+@font-face {
+ font-family: 'Product Sans';
+ src: url('../assets/fonts/product_sans_medium.ttf') format('truetype');
+ font-weight: 500;
+ font-display: swap;
+}
+@font-face {
+ font-family: 'Roboto Medium';
+ src: url('../assets/fonts/roboto_medium.ttf') format('truetype');
+ font-weight: 500;
+ font-display: swap;
+}
+@font-face {
+ font-family: 'Proxima Nova';
+ src: url('../assets/fonts/proxima_nova_bold.otf') format('opentype');
+ font-weight: 700;
+ font-display: swap;
+}
+@font-face {
+ font-family: 'YTSans';
+ src: url('../assets/fonts/ytsans_medium.ttf') format('truetype');
+ font-weight: 500;
+ font-display: swap;
+}
+
+:root {
+ --color-accent: #f04e5c;
+ --color-accent-alpha-10: rgba(240, 78, 92, 0.1);
+ --color-accent-alpha-20: rgba(240, 78, 92, 0.2);
+ --color-accent-alpha-50: rgba(240, 78, 92, 0.5);
+ --color-accent-alpha-70: rgba(240, 78, 92, 0.7);
+
+ --color-screen-bg: #ffffff;
+ --color-surface: #f5f5f5;
+ --color-surface-raised: #f2f2f2;
+
+ --color-primary-text: rgba(0, 0, 0, 0.87);
+ --color-secondary-text: rgba(0, 0, 0, 0.54);
+ --color-tertiary-text: rgba(0, 0, 0, 0.38);
+ --color-hint-text: rgba(0, 0, 0, 0.24);
+
+ --color-icon-tint: rgba(0, 0, 0, 0.54);
+ --color-icon-alt-tint: rgba(0, 0, 0, 0.38);
+
+ --color-border: rgba(0, 0, 0, 0.1);
+ --color-separator: rgba(0, 0, 0, 0.05);
+ --color-ripple: rgba(0, 0, 0, 0.12);
+
+ --color-bottom-nav-bg: #ffffff;
+ --color-bottom-nav-bg-alpha: rgba(255, 255, 255, 0.9);
+ --color-bottom-nav-icon: #757575;
+ --color-bottom-nav-icon-active: #f04e6c;
+ --color-bottom-nav-label: #757575;
+ --color-bottom-nav-label-active: #f04e6c;
+ --color-bottom-nav-indicator: #ffdad7;
+
+ --color-search-bar-bg: #f5f5f5;
+ --color-card-bg: #ffffff;
+
+ --color-shimmer-base: #f5f5f5;
+ --color-shimmer-highlight: #eeeeee;
+
+ --color-green: #73c978;
+ --color-blue: #6979ce;
+ --color-yellow: #ffd468;
+ --color-red: #ff605b;
+ --color-purple: #c373c9;
+
+ --color-badge-bg: #f04e5c;
+ --color-fab-bg: #ffdad7;
+
+ --color-button-primary: #f04e5c;
+ --color-button-primary-text: #ffffff;
+
+ --color-status-bubble: rgba(0, 0, 0, 0.45);
+
+ --release-card-min-width: 124px;
+ --release-card-poster-height: 162px;
+ --release-card-padding-top: 12px;
+ --release-card-padding-bottom: 4px;
+ --release-card-padding-start: 8px;
+ --release-card-padding-end: 8px;
+ --release-card-title-size: 13px;
+ --release-card-text-size: 12px;
+ --release-card-corner-radius: 16px;
+
+ --release-poster-width-regular: 104px;
+ --release-poster-height-regular: 156px;
+ --release-poster-corner-regular: 16px;
+
+ --section-header-height: 56px;
+ --section-header-font-size: 18px;
+ --section-padding-horizontal: 16px;
+
+ --toolbar-height: 56px;
+ --search-bar-height: 48px;
+
+ --bottom-nav-height: 56px;
+
+ --font-body: 'Roboto', 'Roboto Medium', -apple-system, sans-serif;
+ --font-display: 'YTSans', 'Product Sans', 'Roboto Medium', sans-serif;
+ --font-medium: 'Roboto Medium', 'Roboto', sans-serif;
+
+ --focus-ring-color: rgba(240, 78, 92, 0.6);
+ --focus-ring-width: 3px;
+ --focus-scale: 1.05;
+ --focus-transition: 0.15s cubic-bezier(0.4, 0, 0.2, 1);
+}
+
+[data-theme="dark"] {
+ --color-accent: #e0e0e0;
+ --color-accent-alpha-10: rgba(224, 224, 224, 0.1);
+ --color-accent-alpha-20: rgba(224, 224, 224, 0.2);
+ --color-accent-alpha-50: rgba(224, 224, 224, 0.5);
+ --color-accent-alpha-70: rgba(224, 224, 224, 0.7);
+
+ --color-screen-bg: #121212;
+ --color-surface: #252525;
+ --color-surface-raised: #2c2c2c;
+
+ --color-primary-text: rgba(255, 255, 255, 0.87);
+ --color-secondary-text: rgba(255, 255, 255, 0.7);
+ --color-tertiary-text: rgba(255, 255, 255, 0.38);
+ --color-hint-text: rgba(255, 255, 255, 0.24);
+
+ --color-icon-tint: rgba(255, 255, 255, 0.7);
+ --color-icon-alt-tint: rgba(255, 255, 255, 0.38);
+
+ --color-border: rgba(255, 255, 255, 0.1);
+ --color-separator: rgba(255, 255, 255, 0.05);
+ --color-ripple: rgba(255, 255, 255, 0.2);
+
+ --color-bottom-nav-bg: #252525;
+ --color-bottom-nav-bg-alpha: rgba(37, 37, 37, 0.9);
+ --color-bottom-nav-icon: #616161;
+ --color-bottom-nav-icon-active: #e8def7;
+ --color-bottom-nav-label: #616161;
+ --color-bottom-nav-label-active: #e8def7;
+ --color-bottom-nav-indicator: #494458;
+
+ --color-search-bar-bg: #252525;
+ --color-card-bg: #1e1e1e;
+
+ --color-shimmer-base: #212121;
+ --color-shimmer-highlight: #292929;
+
+ --color-green: #70a873;
+ --color-blue: #6974ad;
+ --color-yellow: #db9d39;
+ --color-red: #c24f4e;
+ --color-purple: #a770ac;
+
+ --color-badge-bg: #494458;
+ --color-fab-bg: #494458;
+
+ --color-button-primary: #f04e5c;
+ --color-button-primary-text: #ffffff;
+
+ --color-status-bubble: rgba(0, 0, 0, 0.65);
+
+ --focus-ring-color: rgba(232, 222, 247, 0.6);
+}