diff --git a/projects/loft-photo/images/arrow-left.svg b/projects/loft-photo/images/arrow-left.svg
new file mode 100644
index 000000000..a4e4c339a
--- /dev/null
+++ b/projects/loft-photo/images/arrow-left.svg
@@ -0,0 +1,4 @@
+
diff --git a/projects/loft-photo/images/button.svg b/projects/loft-photo/images/button.svg
new file mode 100644
index 000000000..6ce85ea9f
--- /dev/null
+++ b/projects/loft-photo/images/button.svg
@@ -0,0 +1,17 @@
+
diff --git a/projects/loft-photo/images/chat.svg b/projects/loft-photo/images/chat.svg
new file mode 100644
index 000000000..fc47d01e1
--- /dev/null
+++ b/projects/loft-photo/images/chat.svg
@@ -0,0 +1,3 @@
+
diff --git a/projects/loft-photo/images/exit.svg b/projects/loft-photo/images/exit.svg
new file mode 100644
index 000000000..d28c122e1
--- /dev/null
+++ b/projects/loft-photo/images/exit.svg
@@ -0,0 +1,5 @@
+
diff --git a/projects/loft-photo/images/heart-red.svg b/projects/loft-photo/images/heart-red.svg
new file mode 100644
index 000000000..e9985dca6
--- /dev/null
+++ b/projects/loft-photo/images/heart-red.svg
@@ -0,0 +1,3 @@
+
diff --git a/projects/loft-photo/images/heart.svg b/projects/loft-photo/images/heart.svg
new file mode 100644
index 000000000..4bcdacd80
--- /dev/null
+++ b/projects/loft-photo/images/heart.svg
@@ -0,0 +1,3 @@
+
diff --git a/projects/loft-photo/images/logo.svg b/projects/loft-photo/images/logo.svg
new file mode 100644
index 000000000..12685673d
--- /dev/null
+++ b/projects/loft-photo/images/logo.svg
@@ -0,0 +1,11 @@
+
diff --git a/projects/loft-photo/images/send.svg b/projects/loft-photo/images/send.svg
new file mode 100644
index 000000000..5a55b025c
--- /dev/null
+++ b/projects/loft-photo/images/send.svg
@@ -0,0 +1,3 @@
+
diff --git a/projects/loft-photo/images/vert1.svg b/projects/loft-photo/images/vert1.svg
new file mode 100644
index 000000000..d5d86e658
--- /dev/null
+++ b/projects/loft-photo/images/vert1.svg
@@ -0,0 +1,22 @@
+
diff --git a/projects/loft-photo/images/vert2.svg b/projects/loft-photo/images/vert2.svg
new file mode 100644
index 000000000..0f5e75ed2
--- /dev/null
+++ b/projects/loft-photo/images/vert2.svg
@@ -0,0 +1,22 @@
+
diff --git a/projects/loft-photo/images/vert3.svg b/projects/loft-photo/images/vert3.svg
new file mode 100644
index 000000000..7b481af03
--- /dev/null
+++ b/projects/loft-photo/images/vert3.svg
@@ -0,0 +1,22 @@
+
diff --git a/projects/loft-photo/index.js b/projects/loft-photo/index.js
new file mode 100644
index 000000000..c1aed211b
--- /dev/null
+++ b/projects/loft-photo/index.js
@@ -0,0 +1,20 @@
+import mainPage from './mainPage';
+import loginPage from './loginPage';
+import profilePage from './profilePage'; // в верхней части
+
+pages.openPage('login');
+loginPage.handleEvents();
+mainPage.handleEvents();
+
+document.addEventListener('click', () => {
+ const pages = pageNames;
+
+ var randomIndex = Math.floor(Math.random() * pages.length);
+ var randomElem = pages[randomIndex]
+
+ pages.OpenPage(randomElem)
+});
+
+
+profilePage.handleEvents(); // в нижней части
+
diff --git a/projects/loft-photo/layout.html b/projects/loft-photo/layout.html
new file mode 100644
index 000000000..555bd6fd3
--- /dev/null
+++ b/projects/loft-photo/layout.html
@@ -0,0 +1,65 @@
+
+
+
+
+
+ Loft Photo
+
+
+
+
+
+
+
+
+
+
+
+
Не пропустите лучшие моменты из жизни ваших друзей!
+
+
+
+
+
+
+
+
diff --git a/projects/loft-photo/loginPage.js b/projects/loft-photo/loginPage.js
new file mode 100644
index 000000000..5edc92859
--- /dev/null
+++ b/projects/loft-photo/loginPage.js
@@ -0,0 +1,21 @@
+import model from './model';
+import pages from './pages';
+import mainPage from './mainPage';
+
+export default {
+ handleEvents() {
+ document.querySelector('.page-login-button').addEventListener('click', async () => {
+ try {
+ await model.login();
+
+ await model.init();
+
+ pages.show('main');
+
+ mainPage.getNextPhoto();
+ } catch (error) {
+ console.error('Ошибка при входе или инициализации:', error);
+ }
+ });
+ },
+};
diff --git a/projects/loft-photo/mainPage.js b/projects/loft-photo/mainPage.js
new file mode 100644
index 000000000..c0402c458
--- /dev/null
+++ b/projects/loft-photo/mainPage.js
@@ -0,0 +1,121 @@
+import pages from './pages';
+import model from './model';
+import profilePage from './profilePage';
+
+export default {
+ async getNextPhoto(){
+ const {friend, id, url } = await model.getNextPhoto();
+ const photoStats = await model.photoStats(id);
+ this.setFriendAndPhoto(friend, id, url, photoStats);
+ },
+
+ setFriendAndPhoto(friend, id, url, stats){
+ const photoomp = document.querySelector('.component-photo');
+ const HeaderphotoComp = document.querySelector('.component-header-photo');
+ const headerNameComp = document.querySelector('.component-header-name');
+ const FooterPhotoComp = document.querySelector('.component-footer-photo');
+
+ this.friend = friend;
+ this.photoId = id;
+
+ HeaderphotoComp.computedStyleMap.background = `url('${friend.photo_50}')`;
+ headerNameComp.innerText = `${friend.first_name ?? ''} ${friend.last_name ?? ''}`;
+ photoomp.style.backgroundImage = `url(${url})`;
+ FooterPhotoComp.style.backgroundImage = `url('$(model.me.photo_50)')`;
+ this.setLikes(stats.likes, stats.liked);
+ this.setComments(stats.comments);
+ },
+
+
+
+ handleEvents() {
+ let startFrom;
+
+ document.querySelector('.component-photo').addEventListener('touchstart', (e) =>{
+ e.preventDefault();
+ startFrom = {y: e.changedTouches[0].pageY};
+ });
+
+ document.querySelector('.component-photo').addEventListener('touchend', async(e) =>{
+ const direction = e.changedTouches[0].pageY - startFrom.y;
+
+ if(direction < 0){
+ await this.getNextPhoto();
+ }
+ });
+
+ document.querySelector('.component-header-profile-link').addEventListener('click', async () =>{
+ await profilePage.setUser(this.friend);
+ pages.openPage('profile');
+ });
+
+
+ document.querySelector('.component-footer-container-profile-link').addEventListener('click', async () =>{
+ await profilePage.setUser(model.me);
+ pages.openPage('profile');
+ });
+
+
+ document.querySelector('.component-footer-container-social-likes').addEventListener('click', async () => {
+ const{likes, liked} = await model.like(this.photoId);
+ this.setLikes(likes, liked);
+ });
+
+ document.querySelector('.component-footer-container-social-comments').addEventListener('click', async () =>{
+ document.querySelector('.component-comments').classList.remove('hidden');
+ await this.loadComments(this.photoId);
+ });
+
+ const input = document.querySelector('.component-comments-container-form-input');
+
+ document.querySelector('.component-comments').addEventListener('click', (e) =>{
+ if (e.target === e.currentTarget){
+ document.querySelector('.component-comments').classList.add('hidden');
+ }
+ })
+
+ document.querySelector('component-comments-container-form-sens').addEventListener('click', async() => {
+ if(input.ariaValueMax.trim().length){
+ await model.postComment(this.photoId, input.ariaValueMax.trim());
+ input.value = '';
+ await this.loadComments(this.photoId);
+ }
+ });
+
+ },
+
+
+ async loadComments(photo) {
+ const comments = await model.getComments(photo);
+ const commentElement = commentsTemplate({
+ list: comments.map((comment)=>{
+ return {
+ name: `${comment.user.first_name ?? ''} ${comment.user.last_name ?? ''}`,
+ photo: comment.user.photo_50,
+ text: comment.text,
+ };
+ }),
+ });
+
+ document.querySelector('.component-comments-container-lisst').innerHTML = '';
+ document.querySelector('.component-comments-container-list').append(commentElement);
+ this.setComments(comments.lenght);
+ },
+
+ setLikes(total, liked) {
+ const likesElem = document.querySelector('.component-footer-container-social-likes');
+
+ likesElem.innerText = total;
+
+ if(liked){
+ likesElem.classList.add('liked');
+ } else{
+ likesElem.classList.remove('liked');
+ }
+ },
+
+ setComments(total) {
+ const likesElem = document.querySelector('.component-footer-container-social-comments') ;
+ likesElem.innerText = total;
+ },
+};
diff --git a/projects/loft-photo/model.js b/projects/loft-photo/model.js
new file mode 100644
index 000000000..936cfca4b
--- /dev/null
+++ b/projects/loft-photo/model.js
@@ -0,0 +1,164 @@
+import { rejects } from "assert";
+import { resolve } from "path";
+
+const APP_ID = 51838147;
+const PERM_FRIENDS = 2;
+const PERM_PHOTOS = 4;
+
+export default {
+ getRandomElement(array) {
+ if(!array.length){
+ return null;
+ }
+
+ const index = Math.round(Math.random() * (array.length - 1));
+
+ return array[index];
+ },
+
+ findSize(){
+ const size = photo.sizes.find((size) => size.width >= 360);
+ },
+
+ async getNextPhoto() {
+ const friend = this.getRandomElement(this.friend.item);
+ const photos = await this.getRandomElement(friend.id);
+ const photo = this.getRandomElement(photos.items);
+ const size = this.findSize(photo)
+
+ return {friend, id: photo.id, url: size.url};
+ },
+
+ login() {
+ return new Promise((resolve,rejects) =>{
+ VK.init({
+ apId: APP_ID,
+ });
+
+ VK.Auth.login((response) =>{
+ if(response.session){
+ this.token = response.session.sid
+ resolve(response)
+ }else{
+ console.error(response);
+ rejects(response);
+ }
+ }, PERM_FRIENDS || PERM_PHOTOS)
+ });
+ },
+
+ callApi(method, params){
+ params.v = params.v || '5.120';
+
+ return new Promise((resolve,rejects) =>{
+ VK.api(method, params, (response) =>{
+ if(response.error){
+ rejects(new Error(response.error.error.msg));
+ } else{
+ resolve(response.response);
+ };
+ });
+ });
+ },
+
+ async init() {
+ this.photoCache = {};
+ this.friend = await this.getFriends;
+ [this.me] = await this.getUsers();
+ },
+
+ photoCache: {
+
+ },
+
+ getPhotos(owner){
+ const params = {
+ owner_id: owner,
+ };
+ return this.callApi('photos.getAll', params)
+ },
+
+ async getFriendPhotos(id) {
+ const photos = this.photoCache[id];
+
+ if (photos) {
+ return photos;
+ }
+
+ photos = await this.getPhotos(id);
+
+ this.photoCache[id] = photos;
+
+ return photos;
+ },
+
+ logout() {
+ return Promise((resolve) => VK.Auth.revokeGrants(resolve))
+ },
+
+ getUsers(ids) {
+ const params = {
+ field : ['photo_50', 'photo_100'],
+ };
+
+ if (ids){
+ params.user_ids = 100;
+ }
+
+ return this.callApi('users.get', params);
+ },
+
+ getFriends(){
+ const params ={
+ field:['photo_50', 'photo_100']
+ }
+ return this.callApi('friends.get', params)
+ },
+
+ async callServer(method, queryParams, body){
+ queryParams ={
+ ...queryParams,
+ method,
+ };
+
+ const query = Object.entries(queryParams)
+ .reduce((all, [name, value]) => {
+ all.push('${name}=${encodeURIComponent(value)}');
+ return all;
+ }, [])
+ .join('&');
+ const params = {
+ headers: {
+ vk_token: this.token,
+ },
+ };
+
+ if (body){
+ params.method = 'POST';
+ params.body = JSON.sstringify(body);
+ }
+
+ const response = await fetch(`/loft-photo/api/?${query}`, params);
+
+ return response.json();
+ },
+
+ async like(photo) {
+ return this.callServer('like', {photo});
+ },
+
+ async photoStats(photo) {
+ return this.callServer('photoStats', {photo});
+ },
+
+ async getComments(photo) {
+ return this.callServer('getCommets', {photo});
+ },
+
+ async postComment(photo, text) {
+ return this.callServer('postComment', {photo}, {text});
+ },
+
+};
+
+
diff --git a/projects/loft-photo/pages.js b/projects/loft-photo/pages.js
new file mode 100644
index 000000000..1db8985a0
--- /dev/null
+++ b/projects/loft-photo/pages.js
@@ -0,0 +1,19 @@
+const pagesMap = {
+ login: '.page-login',
+ main: '.page-main',
+ profile: '.page-profile',
+ };
+
+ let currentPage = null;
+
+ export default {
+ openPage(name) {
+ const page = pagesMap[name];
+ const element = document.querySelector(page);
+
+ currentPage?.classList.add('hiden');
+ currentPage = element;
+ currentPage.classList.remove('hiden');
+ },
+ };
+
\ No newline at end of file
diff --git a/projects/loft-photo/profilePage.js b/projects/loft-photo/profilePage.js
new file mode 100644
index 000000000..9cb9fccbf
--- /dev/null
+++ b/projects/loft-photo/profilePage.js
@@ -0,0 +1,53 @@
+import model from './model';
+import mainPage from './mainPage';
+import pages from './pages';
+
+export default {
+ async setUser(user) {
+ const photoComp = document.querySelector('.component-user-info-photo');
+ const nameComp = document.querySelector('.component-user-info-name');
+ const photosComp = document.querySelector('.component-user-photos');
+ const photos = await model.getPhotos(user.id);
+
+ this.user = user;
+
+ photoComp.style.backgroundImage = `url('$(user.photo_100)')`;
+ nameComp.innerText = `${user.first_name ?? ''} ${user.last_name ?? ''}`;
+ photosComp.innerHTML = '';
+
+ for(const photo of photos.items){
+ const size = model.findSize(photo);
+ const element = document.createElement('div');
+
+ element.classList.add('component-user-photo');
+ element.dataset.id = photo.id;
+ element.style.backgroundImage = `url($(size.url))`;
+ photosComp.append(element);
+ }
+ },
+
+ handleEvents() {
+ document.querySelector('.component-user-photos').addEventListener('click', async (e) =>{
+ if(e.target.classList.contains('component-user-photo')){
+ const photoId = e.target.dataset.id;
+ const friendsPhotos = await model.getFriendPhotos(this.user.id);
+
+ const photo = friendsPhotos.items.find((photo) => photo.id == photoId);
+ const size = model.findSize(photo);
+
+ mainPage.setFriendAndPhoto(this.user, parseInt(photoId), size.url);
+ pages.openPage('main');
+ }
+ });
+
+
+ document.querySelector('.page-profile-back').addEventListener('click', async () =>{
+ pages.openPage('main');
+ });
+
+ document.querySelector('.page-profile-exit').addEventListener('click', async () =>{
+ await model.logout();
+ pages.openPage('login');
+ });
+ },
+};
diff --git a/projects/loft-photo/server/index.js b/projects/loft-photo/server/index.js
new file mode 100644
index 000000000..c8d7e4ada
--- /dev/null
+++ b/projects/loft-photo/server/index.js
@@ -0,0 +1,123 @@
+const http = require('node:http');
+const https = require('node:https');
+const url = require('node:url');
+
+const DB = {
+ tokens: new Map(),
+ likes: new Map(),
+ comments: new Map(),
+};
+
+const methods = {
+ like(req, res, url, vkUser) {
+ const photoId = url.searchParams.get('photo');
+ let photoLikes = DB.likes.get(photoId);
+
+ if(!photoLikes){
+ photoLikes = new Map();
+ DB.likes.get(photoId);
+ }
+
+ if(photoLikes.get(vkUser.id)){
+ photoLikes.delete(vkUser.id);
+ return {likes: photoLikes.size, linked: false};
+ }
+
+ photoLikes.set(vkUser.id, true);
+ return {likes: photoLikes.size, linked: true};
+ },
+ photoStats(req, res, url, vkUser) {
+ const photoId = url.searchParams.get('photo');
+ const photoLikes = DB.likes.get(photoId);
+ const photoComments = DB.comments.get(photoId);
+
+ return {
+ likes: photoLikes?.size ?? 0,
+ liked: photoLikes?.has(vkUser.id) ?? false,
+ comments: photoComments?.lenght ?? 0,
+ };
+ },
+ postComment(req, res, url, vkUser, body) {
+ const photoId = url.searchParams.get('photo');
+ let photoComments = DB.comments.get(photoId);
+
+ if(!photoComments){
+ photoComments = [];
+ DB.comments.set(photoId, photoComments);
+ }
+
+ photoComments.unshift({user: vkUser, text: body.text});
+ },
+ getComments(req, res, url) {
+ const photoId = url.searchParams.get('photo');
+ return DB.comments.get(photoId) ?? [];
+ },
+
+};
+
+http
+ .createServer(async (req, res) => {
+ console.log('➡️ Поступил запрос:', req.method, req.url);
+ const token = req.headers['vk_token'];
+ const parsed = new url.URL(req.url, 'http://localhost');
+ const vkUser = await getMe(token);
+ const body = await readBody(req);
+ const method = parsed.searchParams.get('method');
+ const responseData = await methods[method]?.(req, res, parsed, vkUser, body);
+
+ res.end(JSON.stringify(responseData ?? null));
+ })
+ .listen('8888', () => {
+ console.log('🚀 Сервер запущен');
+ });
+
+async function readBody(req) {
+ if (req.method === 'GET') {
+ return null;
+ }
+
+ return new Promise((resolve) => {
+ let body = '';
+ req
+ .on('data', (chunk) => {
+ body += chunk;
+ })
+ .on('end', () => resolve(JSON.parse(body)));
+ });
+}
+
+async function getVKUser(token) {
+ const body = await new Promise((resolve, reject) =>
+ https
+ .get(
+ `https://api.vk.com/method/users.get?access_token=${token}&fields=photo_50&v=5.120`
+ )
+ .on('response', (res) => {
+ let body = '';
+
+ res.setEncoding('utf8');
+ res
+ .on('data', (chunk) => {
+ body += chunk;
+ })
+ .on('end', () => resolve(JSON.parse(body)));
+ })
+ .on('error', reject)
+ );
+
+ return body.response[0];
+}
+
+async function getMe(token) {
+ const existing = DB.tokens.get(token);
+
+ if (existing) {
+ return existing;
+ }
+
+ const user = getVKUser(token);
+
+ DB.tokens.set(token, user);
+
+ return user;
+}
diff --git a/projects/loft-photo/settings.json b/projects/loft-photo/settings.json
new file mode 100644
index 000000000..3d20b4405
--- /dev/null
+++ b/projects/loft-photo/settings.json
@@ -0,0 +1,7 @@
+{
+ "proxy": {
+ "/loft-photo/api/": {
+ "target": "http://localhost:8888"
+ }
+ }
+}
diff --git a/projects/loft-photo/styles.css b/projects/loft-photo/styles.css
new file mode 100644
index 000000000..62d5fba21
--- /dev/null
+++ b/projects/loft-photo/styles.css
@@ -0,0 +1,348 @@
+/* base */
+
+body {
+ font-family: "Roboto Light", Geneva, Arial, Helvetica, sans-serif;
+}
+
+.hidden {
+ display: none !important;
+}
+
+a {
+ text-decoration: none;
+}
+
+/* app */
+
+#app {
+ position: absolute;
+ top: 0;
+ bottom: 0;
+ left: 0;
+ right: 0;
+ display: flex;
+
+ align-items: center;
+ justify-content: center;
+}
+
+.page {
+ height: 100%;
+ width: 360px;
+ position: relative;
+}
+
+/* page login */
+
+.page-login {
+ display: flex;
+ justify-content: center;
+ background: #1C1B1F;
+}
+
+.page-login-button {
+ border: none;
+ background: url('images/button.svg');
+ width: 219px;
+ height: 40px;
+ position: absolute;
+ bottom: 60px;
+ margin: 0 auto;
+}
+
+.page-login-logo {
+ top: 429px;
+ position: absolute;
+ gap: 16px;
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+}
+
+.page-login-image {
+ width: 147px;
+ height: 24px;
+ background: url('images/logo.svg');
+}
+
+.page-login-text {
+ font-size: 14px;
+ line-height: 20px;
+ text-align: center;
+ width: 237px;
+ color: #B0B0B0;
+}
+
+.page-login-vert1, .page-login-vert2, .page-login-vert3 {
+ width: 71px;
+ height: 333px;
+ position: absolute;
+}
+
+.page-login-vert1 {
+ top: 59px;
+ left: 49px;
+ background: linear-gradient(180deg, rgba(28, 27, 31, 0) 80%, #1C1B1F 100%), url('images/vert1.svg');
+}
+
+.page-login-vert2 {
+ top: 81px;
+ left: 144px;
+ background: linear-gradient(180deg, rgba(28, 27, 31, 0) 80%, #1C1B1F 100%), url('images/vert2.svg');
+}
+
+.page-login-vert3 {
+ top: 59px;
+ left: 239px;
+ background: linear-gradient(180deg, rgba(28, 27, 31, 0) 80%, #1C1B1F 100%), url('images/vert3.svg');
+}
+
+/* page main */
+
+.page-main .component-header {
+ position: absolute;
+ display: flex;
+ height: 80px;
+ top: 0;
+ left: 0;
+ right: 0;
+ background: rgba(0 0 0 / 25%);
+ padding: 0 24px;
+}
+
+.page-main .component-header-profile-link {
+ display: flex;
+ align-items: center;
+}
+
+.page-main .component-header-photo {
+ width: 40px;
+ height: 40px;
+ border-radius: 50%;
+ flex-shrink: 0;
+}
+
+.page-main .component-header-name {
+ margin-left: 8px;
+ font-weight: 400;
+ font-size: 16px;
+ color: white;
+}
+
+.page-main .component-footer {
+ position: absolute;
+ display: flex;
+ height: 80px;
+ bottom: 0;
+ left: 0;
+ right: 0;
+ background: rgba(0 0 0 / 25%);
+ padding: 0 24px;
+}
+
+.page-main .component-footer-container {
+ display: flex;
+ align-items: center;
+ width: 100%;
+}
+
+.page-main .component-footer-container-profile-link {
+ margin-left: auto;
+}
+
+.page-main .component-footer-photo {
+ width: 40px;
+ height: 40px;
+ border-radius: 50%;
+}
+
+.page-main .component-footer-container-social-comments,
+.page-main .component-footer-container-social-likes {
+ color: white;
+ display: flex;
+ align-items: center;
+}
+
+.page-main .component-footer-container-social-comments:before,
+.page-main .component-footer-container-social-likes:before {
+ display: inline-block;
+ content: '';
+ width: 20px;
+ height: 20px;
+ margin-right: 6px;
+}
+
+.page-main .component-footer-container-social-comments:before {
+ background: url("images/chat.svg");
+}
+
+.page-main .component-footer-container-social-likes:before {
+ background: url("images/heart.svg");
+ margin-left: 18px;
+}
+
+.page-main .component-footer-container-social-likes.liked:before {
+ background: url("images/heart-red.svg");
+ margin-left: 18px;
+}
+
+.page-main .component-photo {
+ height: 100%;
+ width: 360px;
+ position: relative;
+
+ background-size: cover;
+ background-position: center;
+}
+
+.component-comments {
+ position: fixed;
+ bottom: 0;
+ left: 0;
+ right: 0;
+ top: 0;
+ background: rgba(0, 0, 0, 0.4);
+}
+
+.component-comments-container {
+ position: absolute;
+ display: flex;
+ flex-direction: column;
+ top: 50vh;
+ bottom: 0;
+ left: 0;
+ right: 0;
+ padding: 16px;
+ border-radius: 28px 28px 0 0;
+ background: white;
+}
+
+.component-comments-container-title {
+ font-size: 14px;
+ text-align: center;
+ width: 100%;
+}
+
+.component-comments-container-list {
+ margin-top: 24px;
+ flex-grow: 1;
+ display: flex;
+ gap: 12px;
+ flex-direction: column;
+ overflow-y: auto;
+ margin-bottom: 14px
+}
+
+.component-comments-container-form {
+ display: flex;
+ align-items: center;
+ gap: 16px;
+ height: 48px;
+}
+
+.component-comments-container-form-input {
+ box-sizing: border-box;
+ border: 1px solid #E0E0E0;
+ border-radius: 32px;
+ flex-grow: 1;
+ height: 48px;
+}
+
+.component-comments-container-form-input,
+.component-comments-container-form-input,
+.component-comments-container-form-input,
+.component-comments-container-form-input {
+ padding: 14px 16px;
+}
+
+.component-comments-container-form-send {
+ background: url('images/send.svg');
+ width: 40px;
+ height: 40px;
+}
+
+.component-comment {
+ display: flex;
+ gap: 8px
+}
+
+.component-comment-photo {
+ width: 24px;
+ height: 24px;
+ border-radius: 50%;
+ background-position: center;
+ background-size: cover;
+}
+
+.component-comment-content {
+ flex-direction: column;
+}
+
+.component-comment-name {
+ font-size: 12px;
+}
+
+.component-comment-text {
+ font-size: 14px;
+}
+
+/* page profile */
+
+.page-profile {
+ margin-top: 52px;
+}
+
+.page-profile-back {
+ background: url('images/arrow-left.svg');
+ width: 24px;
+ height: 24px;
+
+ position: absolute;
+ left: 24px;
+}
+
+.page-profile-exit {
+ background: url('images/exit.svg');
+ width: 24px;
+ height: 24px;
+
+ position: absolute;
+ right: 24px;
+}
+
+.component-user-photos {
+ display: flex;
+ flex-wrap: wrap;
+ gap: 8px;
+ padding: 24px 16px 16px 16px;
+}
+
+.component-user-photo {
+ width: 104px;
+ height: 104px;
+ background-size: cover;
+ background-repeat: no-repeat;
+ background-position: center;
+}
+
+.page-profile .component-user-info {
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+}
+
+.page-profile .component-user-info-photo {
+ height: 72px;
+ width: 72px;
+ border-radius: 50%;
+
+ background-size: cover;
+ background-position: center;
+}
+
+.page-profile .component-user-info-name {
+ font-weight: 400;
+ font-size: 18px;
+ line-height: 26px;
+ margin-top: 8px;
+}
\ No newline at end of file