diff --git a/app/components/SearchBar.module.scss b/app/components/SearchBar.module.scss
index 3db5b16..3893eae 100644
--- a/app/components/SearchBar.module.scss
+++ b/app/components/SearchBar.module.scss
@@ -4,11 +4,18 @@
position: sticky;
top: 0;
z-index: 20;
- padding: 12px 8px;
+ padding: 12px;
background: #fff;
display: flex;
- align-items: center;
justify-content: center;
+ width: auto;
+ gap: 15px;
+}
+
+.inner {
+ width: 100%;
+ display: flex;
+ align-items: center;
gap: 15px;
}
diff --git a/app/components/SearchBar.tsx b/app/components/SearchBar.tsx
index 65b2bda..d325338 100644
--- a/app/components/SearchBar.tsx
+++ b/app/components/SearchBar.tsx
@@ -2,19 +2,22 @@ import { Form } from '@remix-run/react';
import styles from './SearchBar.module.scss';
-export const SearchBar = () => {
+export const SearchBar = ({ defaultQuery = '' }: { defaultQuery?: string }) => {
return (
);
};
diff --git a/app/features/music/pages/music.user.module.scss b/app/features/music/pages/music.user.module.scss
index b4a7e02..59680eb 100644
--- a/app/features/music/pages/music.user.module.scss
+++ b/app/features/music/pages/music.user.module.scss
@@ -26,7 +26,6 @@
}
}
-// 유저 정보(작성자) 스타일
.author-info {
margin-bottom: 2rem;
display: flex;
diff --git a/app/features/profile/components/SearchSongInput.tsx b/app/features/profile/components/SearchSongInput.tsx
deleted file mode 100644
index bacaa8a..0000000
--- a/app/features/profile/components/SearchSongInput.tsx
+++ /dev/null
@@ -1,73 +0,0 @@
-import { Await, Form } from '@remix-run/react';
-import { Suspense } from 'react';
-
-import type { MusicInfo } from 'app/external/music/IMusicSearchAPI';
-import styles from 'app/features/profile/pages/searchSong.module.scss';
-
-const PLACEHOLDER = '/images/features/profile/album_default2.png';
-
-interface Props {
- songs: Promise | undefined;
- onSelect: (song: MusicInfo, index: number) => void;
-}
-
-export default function SearchSongInput({ onSelect, songs }: Props) {
- return (
-
-
-
-
-
- );
-}
-
-const SearchedSongList = ({
- songs,
- onSelect,
-}: {
- songs: Promise | undefined;
- onSelect: (song: MusicInfo, index: number) => void;
-}) => {
- return (
-
- 검색 중...}>
-
- {(resolvedSongs) => (
- <>
- {resolvedSongs?.length === 0 && 검색 결과가 없습니다.
}
- {resolvedSongs?.map((song, i) => (
- - onSelect(song, i)}
- >
- }>
-
- {(albumCover) => (
-
{
- e.currentTarget.src = PLACEHOLDER;
- }}
- />
- )}
-
-
-
-
{song.title}
-
{song.artist}
-
{song.album}
-
-
- ))}
- >
- )}
-
-
-
- );
-};
diff --git a/app/features/profile/components/SearchSongInputloader.tsx b/app/features/profile/components/SearchSongInputloader.tsx
deleted file mode 100644
index 312abcb..0000000
--- a/app/features/profile/components/SearchSongInputloader.tsx
+++ /dev/null
@@ -1,10 +0,0 @@
-import { searchMusic } from 'app/external/music/SearchMusic';
-
-export const searchSongInputLoader = ({ request }: { request: Request }) => {
- const searchQuery = new URL(request.url).searchParams.get('query');
- if (!searchQuery) {
- return undefined;
- }
-
- return searchMusic.searchSongWithQuery(searchQuery);
-};
diff --git a/app/features/profile/loader.ts b/app/features/profile/loader.ts
index b94fe38..800add3 100644
--- a/app/features/profile/loader.ts
+++ b/app/features/profile/loader.ts
@@ -5,7 +5,6 @@ import { getCurrentDBUser, getCurrentUser, requireUserOwnership } from 'app/exte
import { buildSpotifyTrackUrl, getSpotifyEmbed } from 'app/external/music/SpotifyOEmbed';
import createLoader from 'app/utils/createLoader';
-import { searchSongInputLoader } from './components/SearchSongInputloader';
import {
fetchAccountSettingsData,
fetchFollowers,
diff --git a/app/features/profile/pages/addTodaySong.module.scss b/app/features/profile/pages/addTodaySong.module.scss
index 5c9921d..14c8106 100644
--- a/app/features/profile/pages/addTodaySong.module.scss
+++ b/app/features/profile/pages/addTodaySong.module.scss
@@ -1,48 +1,48 @@
-@use 'styles/layout' as *;
+// @use 'styles/layout' as *;
-.add-today-song-container {
- display: flex;
- padding: 36px 16px;
- flex-direction: column;
- border-radius: 8px;
-}
+// .add-today-song-container {
+// display: flex;
+// padding: 36px 16px;
+// flex-direction: column;
+// border-radius: 8px;
+// }
-.title-box {
- display: flex;
- align-items: center;
- .back-button {
- color: $black-500;
- font-size: 24px;
- font-weight: 600;
- margin-left: 16px;
- margin: 0;
- text-decoration: none;
- }
- .title {
- flex: 1 0 auto;
- font-size: 24px;
- font-weight: 600;
- margin-left: 16px;
- margin: 0;
- text-align: center;
- }
-}
+// .title-box {
+// display: flex;
+// align-items: center;
+// .back-button {
+// color: $black-500;
+// font-size: 24px;
+// font-weight: 600;
+// margin-left: 16px;
+// margin: 0;
+// text-decoration: none;
+// }
+// .title {
+// flex: 1 0 auto;
+// font-size: 24px;
+// font-weight: 600;
+// margin-left: 16px;
+// margin: 0;
+// text-align: center;
+// }
+// }
-.search-section {
- margin-top: 24px;
- h2 {
- font-size: 20px;
- font-weight: 600;
- margin-bottom: 16px;
- }
-}
+// .search-section {
+// margin-top: 24px;
+// h2 {
+// font-size: 20px;
+// font-weight: 600;
+// margin-bottom: 16px;
+// }
+// }
-.initial-song-container {
- margin-top: 24px;
- h3 {
- margin: 0;
- font-size: 20px;
- font-weight: 600;
- margin-bottom: 16px;
- }
-}
+// .initial-song-container {
+// margin-top: 24px;
+// h3 {
+// margin: 0;
+// font-size: 20px;
+// font-weight: 600;
+// margin-bottom: 16px;
+// }
+// }
diff --git a/app/features/profile/pages/addTodaySong.tsx b/app/features/profile/pages/addTodaySong.tsx
index c88f914..3eaf4b9 100644
--- a/app/features/profile/pages/addTodaySong.tsx
+++ b/app/features/profile/pages/addTodaySong.tsx
@@ -1,60 +1,60 @@
-import { Link, useLoaderData, useSubmit } from '@remix-run/react';
-
-import type { MusicInfo } from 'app/external/music/IMusicSearchAPI';
-import SongItem, { type SimpleSong } from 'app/features/profile/components/SongItem';
-import styles from 'app/features/profile/pages/addTodaySong.module.scss';
-
-import SearchSongInput from '../components/SearchSongInput';
-import { addTodaySongLoader } from '../loader';
-
-export default function AddTodaySongPage() {
- const { initialSong, songs } = useLoaderData();
- const savePickedSong = useSavePickedSong();
-
- return (
-
-
-
- {'<'}
-
-
오늘의 추천곡 수정
-
-
-
-
- {initialSong && (
-
-
현재 추천곡
-
-
- )}
-
- );
-}
-
-const useSavePickedSong = () => {
- const submit = useSubmit();
-
- return async (song: MusicInfo) => {
- const ok = window.confirm(`"${song.title}" – ${song.artist}\n오늘의 추천곡으로 등록하시겠습니까?`);
- if (!ok) return;
-
- const simple: SimpleSong = {
- id: 0,
- title: song.title,
- artist: song.artist,
- album: song.album ?? null,
- thumbnailUrl: song.albumCover ? await song.albumCover : null,
- };
-
- const fd = new FormData();
- fd.append('title', simple.title);
- fd.append('artist', simple.artist);
- fd.append('album', simple.album ?? '');
- fd.append('thumbnailUrl', simple.thumbnailUrl ?? '');
- fd.append('spotifyId', song.spotifyId ?? '');
- submit(fd, { method: 'post' });
- };
-};
+// import { Link, useLoaderData, useSubmit } from '@remix-run/react';
+
+// import type { MusicInfo } from 'app/external/music/IMusicSearchAPI';
+// import styles from 'app/features/profile/pages/addTodaySong.module.scss';
+// import SongItem, { type SimpleSong } from 'app/features/search/components/SongItem';
+
+// import SearchSongInput from '../../search/components/SearchSongInput';
+// import { addTodaySongLoader } from '../loader';
+
+// export default function AddTodaySongPage() {
+// const { initialSong, songs } = useLoaderData();
+// const savePickedSong = useSavePickedSong();
+
+// return (
+//
+//
+//
+// {'<'}
+//
+//
오늘의 추천곡 수정
+//
+
+//
+
+// {initialSong && (
+//
+//
현재 추천곡
+//
+//
+// )}
+//
+// );
+// }
+
+// const useSavePickedSong = () => {
+// const submit = useSubmit();
+
+// return async (song: MusicInfo) => {
+// const ok = window.confirm(`"${song.title}" – ${song.artist}\n오늘의 추천곡으로 등록하시겠습니까?`);
+// if (!ok) return;
+
+// const simple: SimpleSong = {
+// id: 0,
+// title: song.title,
+// artist: song.artist,
+// album: song.album ?? null,
+// thumbnailUrl: song.albumCover ? await song.albumCover : null,
+// };
+
+// const fd = new FormData();
+// fd.append('title', simple.title);
+// fd.append('artist', simple.artist);
+// fd.append('album', simple.album ?? '');
+// fd.append('thumbnailUrl', simple.thumbnailUrl ?? '');
+// fd.append('spotifyId', song.spotifyId ?? '');
+// submit(fd, { method: 'post' });
+// };
+// };
diff --git a/app/features/profile/pages/editList.tsx b/app/features/profile/pages/editList.tsx
index 0f4be99..81f535b 100644
--- a/app/features/profile/pages/editList.tsx
+++ b/app/features/profile/pages/editList.tsx
@@ -17,7 +17,7 @@ import { Link } from '@remix-run/react';
import { useState } from 'react';
import styles from './editList.module.scss';
-import SongItem from '../components/SongItem';
+import SongItem from '../../search/components/SongItem';
import { SortableItem } from '../components/SortableItem'; // 새로 추가된 컴포넌트
export default function EditList() {
diff --git a/app/features/profile/pages/searchSong.module.scss b/app/features/profile/pages/searchSong.module.scss
deleted file mode 100644
index 8a14db4..0000000
--- a/app/features/profile/pages/searchSong.module.scss
+++ /dev/null
@@ -1,90 +0,0 @@
-.searchWrapper {
- background-color: #f2f4f5;
- padding: 24px;
- border-radius: 8px;
-
- margin: 16px 24px;
-}
-
-.formRow {
- display: flex;
- gap: 8px;
-}
-
-.input {
- flex: 1;
- padding: 8px 12px;
- border: 1px solid #d1d5db;
- border-radius: 100px;
- font-size: 14px;
-}
-
-.button {
- padding: 8px 16px;
- border: none;
- background-color: #2563eb;
- color: #fff;
- border-radius: 4px;
- cursor: pointer;
-
- &:disabled {
- opacity: 0.6;
- cursor: default;
- }
-}
-
-.songList {
- margin-top: 16px;
- display: flex;
- flex-direction: column;
- gap: 12px;
- padding: 0;
-}
-
-.songItem {
- display: flex;
- gap: 16px;
- align-items: center;
- padding: 12px;
- background-color: #fff;
- border: 1px solid #e5e7eb;
- border-radius: 6px;
- cursor: pointer;
-
- &:hover {
- background-color: #f9fafb;
- }
-}
-
-.cover {
- width: 64px;
- height: 64px;
- object-fit: cover;
- border-radius: 4px;
- background-color: #f3f4f6;
-}
-
-.texts {
- overflow: hidden;
-}
-
-.title {
- font-weight: 600;
- white-space: nowrap;
- overflow: hidden;
- text-overflow: ellipsis;
- margin-bottom: 2px;
-}
-
-.artist,
-.album {
- font-size: 12px;
- color: #6b7280;
- white-space: nowrap;
- overflow: hidden;
- text-overflow: ellipsis;
-}
-
-.empty {
- color: #9ca3af;
-}
diff --git a/app/features/search/action.ts b/app/features/search/action.ts
new file mode 100644
index 0000000..e69de29
diff --git a/app/features/search/components/SearchSongInput.tsx b/app/features/search/components/SearchSongInput.tsx
new file mode 100644
index 0000000..f0badd2
--- /dev/null
+++ b/app/features/search/components/SearchSongInput.tsx
@@ -0,0 +1,73 @@
+// import { Await, Form } from '@remix-run/react';
+// import { Suspense } from 'react';
+
+// import type { MusicInfo } from 'app/external/music/IMusicSearchAPI';
+// import styles from 'app/features/profile/pages/searchSong.module.scss';
+
+// const PLACEHOLDER = '/images/features/profile/album_default2.png';
+
+// interface Props {
+// songs: Promise | undefined;
+// onSelect: (song: MusicInfo, index: number) => void;
+// }
+
+// export default function SearchSongInput({ onSelect, songs }: Props) {
+// return (
+//
+//
+
+//
+//
+// );
+// }
+
+// const SearchedSongList = ({
+// songs,
+// onSelect,
+// }: {
+// songs: Promise | undefined;
+// onSelect: (song: MusicInfo, index: number) => void;
+// }) => {
+// return (
+//
+// 검색 중...}>
+//
+// {(resolvedSongs) => (
+// <>
+// {resolvedSongs?.length === 0 && 검색 결과가 없습니다.
}
+// {resolvedSongs?.map((song, i) => (
+// - onSelect(song, i)}
+// >
+// }>
+//
+// {(albumCover) => (
+//
{
+// e.currentTarget.src = PLACEHOLDER;
+// }}
+// />
+// )}
+//
+//
+//
+//
{song.title}
+//
{song.artist}
+//
{song.album}
+//
+//
+// ))}
+// >
+// )}
+//
+//
+//
+// );
+// };
diff --git a/app/features/search/components/SearchSongInputloader.tsx b/app/features/search/components/SearchSongInputloader.tsx
new file mode 100644
index 0000000..77dcdfe
--- /dev/null
+++ b/app/features/search/components/SearchSongInputloader.tsx
@@ -0,0 +1,5 @@
+import { searchMusic } from 'app/external/music/SearchMusic';
+
+export const searchSongInputLoader = ({ searchQuery }: { searchQuery: string }) => {
+ return searchMusic.searchSongWithQuery(searchQuery);
+};
diff --git a/app/features/profile/components/SongItem.module.scss b/app/features/search/components/SongItem.module.scss
similarity index 100%
rename from app/features/profile/components/SongItem.module.scss
rename to app/features/search/components/SongItem.module.scss
diff --git a/app/features/profile/components/SongItem.tsx b/app/features/search/components/SongItem.tsx
similarity index 100%
rename from app/features/profile/components/SongItem.tsx
rename to app/features/search/components/SongItem.tsx
diff --git a/app/features/search/components/searchSong.module.scss b/app/features/search/components/searchSong.module.scss
new file mode 100644
index 0000000..608bbf1
--- /dev/null
+++ b/app/features/search/components/searchSong.module.scss
@@ -0,0 +1,90 @@
+// .searchWrapper {
+// background-color: #f2f4f5;
+// padding: 24px;
+// border-radius: 8px;
+
+// margin: 16px 24px;
+// }
+
+// .formRow {
+// display: flex;
+// gap: 8px;
+// }
+
+// .input {
+// flex: 1;
+// padding: 8px 12px;
+// border: 1px solid #d1d5db;
+// border-radius: 100px;
+// font-size: 14px;
+// }
+
+// .button {
+// padding: 8px 16px;
+// border: none;
+// background-color: #2563eb;
+// color: #fff;
+// border-radius: 4px;
+// cursor: pointer;
+
+// &:disabled {
+// opacity: 0.6;
+// cursor: default;
+// }
+// }
+
+// .songList {
+// margin-top: 16px;
+// display: flex;
+// flex-direction: column;
+// gap: 12px;
+// padding: 0;
+// }
+
+// .songItem {
+// display: flex;
+// gap: 16px;
+// align-items: center;
+// padding: 12px;
+// background-color: #fff;
+// border: 1px solid #e5e7eb;
+// border-radius: 6px;
+// cursor: pointer;
+
+// &:hover {
+// background-color: #f9fafb;
+// }
+// }
+
+// .cover {
+// width: 64px;
+// height: 64px;
+// object-fit: cover;
+// border-radius: 4px;
+// background-color: #f3f4f6;
+// }
+
+// .texts {
+// overflow: hidden;
+// }
+
+// .title {
+// font-weight: 600;
+// white-space: nowrap;
+// overflow: hidden;
+// text-overflow: ellipsis;
+// margin-bottom: 2px;
+// }
+
+// .artist,
+// .album {
+// font-size: 12px;
+// color: #6b7280;
+// white-space: nowrap;
+// overflow: hidden;
+// text-overflow: ellipsis;
+// }
+
+// .empty {
+// color: #9ca3af;
+// }
diff --git a/app/features/search/loader.ts b/app/features/search/loader.ts
index 86115cb..7717a2e 100644
--- a/app/features/search/loader.ts
+++ b/app/features/search/loader.ts
@@ -2,17 +2,16 @@ import { data } from '@remix-run/node';
import createLoader from 'app/utils/createLoader';
-import { findUserByHandleSim, getRecommendedUsers } from './services';
+import { searchSongInputLoader } from './components/SearchSongInputloader';
-export const searchLoader = createLoader(async ({ db, request }) => {
+export const searchLoader = createLoader(async ({ request }) => {
const url = new URL(request.url);
- const handle = url.searchParams.get('handle');
- const recommendedUsersPromise = getRecommendedUsers(db)();
- const searchResultsPromise = handle ? findUserByHandleSim(db)({ handle }) : Promise.resolve(null);
- const [recommendedUsers, searchResults] = await Promise.all([recommendedUsersPromise, searchResultsPromise]);
+ const query = url.searchParams.get('search') ?? '';
+
+ const songs = searchSongInputLoader({ searchQuery: query });
+
return data({
- recommendedUsers,
- searchResults,
- query: handle,
+ query,
+ songs,
});
});
diff --git a/app/features/search/pages/search.module.scss b/app/features/search/pages/search.module.scss
index 7f3ffcb..5fecb89 100644
--- a/app/features/search/pages/search.module.scss
+++ b/app/features/search/pages/search.module.scss
@@ -1,73 +1,52 @@
@use 'styles/layout' as *;
-.wrapper {
- padding: 2rem 1rem;
- font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif;
+.root {
+ padding: 0 0 10px;
}
-.searchContainer {
+.pageRow {
+ width: 100%;
display: flex;
- gap: 0.5rem;
- margin-bottom: 2.5rem;
- margin-top: 2rem;
- align-items: center;
+ justify-content: center;
}
-.searchInputWrapper {
- position: relative;
- flex-grow: 1;
-
- svg {
- position: absolute;
- left: 0.8rem;
- top: 50%;
- transform: translateY(-50%);
- color: #adb5bd;
- width: 18px;
- height: 18px;
- }
-}
+.side {
+ display: flex;
+ justify-content: center;
+ align-items: flex-start;
+ padding: 14px 10px;
-.searchInput {
- width: 93%;
- height: 39px;
- border-radius: 8px;
- border: 1px solid #dee2e6;
- padding: 0.5rem 1rem 0.5rem 2.5rem;
- font-size: 0.95rem;
- transition:
- border-color 0.2s,
- box-shadow 0.2s;
-
- &:focus {
- outline: none;
- border-color: #4dabf7;
- box-shadow: 0 0 0 3px rgba(77, 171, 247, 0.2);
+ position: sticky;
+ align-self: flex-start;
+
+ @include tablet {
+ display: none;
+ }
+ @include mobile {
+ display: none;
}
}
-.searchButton {
- background-color: #4dabf7;
- color: #fff;
- border: none;
- width: 65px;
- height: 55px;
- flex-shrink: 0;
- border-radius: 8px;
- cursor: pointer;
- transition: background-color 0.2s;
+.content {
display: flex;
- align-items: center;
- justify-content: center;
-
- &:hover {
- background-color: #339af0;
- }
+ flex-direction: column;
+ width: 100%;
+ max-width: 800px;
+}
- svg {
- width: 20px;
- height: 20px;
- }
+.backButton {
+ position: static;
+ display: inline-flex;
+ display: inline-flex;
+ align-items: center;
+ height: 36px;
+ border: 1px solid white;
+ background: white;
+ color: #333;
+ font-weight: 600;
+ cursor: pointer;
+ color: $primary-500;
+ font-size: 18px;
}
.title {
@@ -94,3 +73,63 @@
grid-template-columns: 1fr;
}
}
+
+.formRow {
+ display: flex;
+ gap: 8px;
+}
+
+.songList {
+ margin: 10px 16px;
+ display: flex;
+ flex-direction: column;
+ gap: 12px;
+ padding: 0;
+}
+
+.empty {
+ color: #9ca3af;
+}
+
+.songItem {
+ display: flex;
+ gap: 16px;
+ align-items: center;
+ background-color: #fff;
+ border: 1px solid #e5e7eb;
+ border-radius: 6px;
+ cursor: pointer;
+
+ &:hover {
+ background-color: #f9fafb;
+ }
+}
+
+.cover {
+ width: 64px;
+ height: 64px;
+ object-fit: cover;
+ border-radius: 4px;
+ background-color: #f3f4f6;
+}
+
+.texts {
+ overflow: hidden;
+}
+
+.title {
+ font-weight: 600;
+ white-space: nowrap;
+ overflow: hidden;
+ text-overflow: ellipsis;
+ margin-bottom: 2px;
+}
+
+.artist,
+.album {
+ font-size: 12px;
+ color: #6b7280;
+ white-space: nowrap;
+ overflow: hidden;
+ text-overflow: ellipsis;
+}
diff --git a/app/features/search/pages/search.tsx b/app/features/search/pages/search.tsx
index 91db424..725e86f 100644
--- a/app/features/search/pages/search.tsx
+++ b/app/features/search/pages/search.tsx
@@ -1,60 +1,64 @@
-import { useLoaderData, Form } from '@remix-run/react';
+import { Await, useNavigate, useLoaderData } from '@remix-run/react';
+import { Suspense } from 'react';
+
+import { SearchBar } from 'app/components/SearchBar';
+import { MusicInfo } from 'app/external/music/IMusicSearchAPI';
+import SongItem, { type SimpleSong } from 'app/features/search/components/SongItem';
import styles from './search.module.scss';
-import { SearchIcon } from '../components/Icons';
-import { ProfileCard } from '../components/ProfileCard';
import { searchLoader } from '../loader';
-export default function SearchPage() {
- const { recommendedUsers, searchResults, query } = useLoaderData();
+const PLACEHOLDER = '/images/features/profile/album_default2.png';
- const isSearchMode = !!query;
+export default function SearchPage() {
+ const { query, songs } = useLoaderData();
+ const navigate = useNavigate();
return (
-
+
+
+
);
-}
+};
diff --git a/app/routes/profile.$userId.addTodaySong.tsx b/app/routes/profile.$userId.addTodaySong.tsx
index 71c1508..3307fa9 100644
--- a/app/routes/profile.$userId.addTodaySong.tsx
+++ b/app/routes/profile.$userId.addTodaySong.tsx
@@ -1,12 +1,12 @@
-import type { MetaFunction } from '@remix-run/node';
+// import type { MetaFunction } from '@remix-run/node';
-import { addTodaySongAction } from 'app/features/profile/action';
-import { addTodaySongLoader } from 'app/features/profile/loader';
-import AddTodaySongPage from 'app/features/profile/pages/addTodaySong';
+// import { addTodaySongAction } from 'app/features/profile/action';
+// import { addTodaySongLoader } from 'app/features/profile/loader';
+// import AddTodaySongPage from 'app/features/profile/pages/addTodaySong';
-export const meta: MetaFunction = () => [{ title: '오늘의 추천곡 수정' }];
+// export const meta: MetaFunction = () => [{ title: '오늘의 추천곡 수정' }];
-export const loader = addTodaySongLoader;
-export const action = addTodaySongAction;
+// export const loader = addTodaySongLoader;
+// export const action = addTodaySongAction;
-export default AddTodaySongPage;
+// export default AddTodaySongPage;