294 lines
20 KiB
Markdown
294 lines
20 KiB
Markdown
# ⚡ Test Project: Module A — Speed Test
|
||
|
||
## 1. Project Overview
|
||
|
||
This assessment is a **Speed Test** designed to quickly and accurately evaluate practical proficiency across web technologies. Competitors must complete a total of 20 independent tasks across four categories — **A (Design)**, **B (Layout)**, **C (Frontend)**, and **D (Backend)** — within the allotted time. Each task is independent and may be attempted in any order.
|
||
|
||
---
|
||
|
||
## 2. Tech Stack & Constraints
|
||
|
||
| Category | Allowed Technologies | Restrictions |
|
||
| :--- | :--- | :--- |
|
||
| **A. Design** | GIMP | No other image editors (e.g., Photoshop) permitted |
|
||
| **B. Layout** | HTML, CSS | JavaScript is not permitted |
|
||
| **C. Frontend** | JavaScript (Vanilla) | No external libraries or frameworks permitted |
|
||
| **D. Backend** | PHP, MySQL | Database must be connected using PDO |
|
||
|
||
* All deliverables will be evaluated in a desktop Chrome browser.
|
||
* Save each task's deliverable in a subdirectory named after the task ID under `/module_a/`. (e.g., `/module_a/a1/`, `/module_a/b1/`)
|
||
* Design tasks must include the GIMP source file (`.xcf`) in the submission directory.
|
||
|
||
---
|
||
|
||
## 3. Tasks
|
||
|
||
### 🎨 A. Design — GIMP Proficiency
|
||
|
||
> **Provided files**: Files for A1 (`source.png`), A2 (`base.jpg`), and A3 (`photo.jpg`, `mask.png`) are pre-placed in each task's working directory. A4 and A5 have no provided files; competitors must create all assets from scratch.
|
||
|
||
#### **A1. Layer Compositing and Text Placement** `Easy`
|
||
|
||
Create an image in GIMP using a layered structure.
|
||
|
||
* Canvas size: **800×400px**, Resolution: **72dpi**
|
||
* **Layer 1**: Fill the background with the solid color `#2C3E50`.
|
||
* **Layer 2**: Open the provided image (`source.png`), place it at the **center** of the canvas, and set its opacity to **70%**.
|
||
* **Layer 3**: Add the text **"Hello, GIMP"** in white (`#FFFFFF`) at 36pt, and position it at the top-left of the canvas **(x: 20px, y: 20px)**.
|
||
* Save the project as a `.xcf` file first. Then flatten all layers (Flatten Image) and export the result as `result.png`.
|
||
|
||
#### **A2. Selection and Layer Compositing** `Easy`
|
||
|
||
Apply shape effects to an image using GIMP's selection tools and layers.
|
||
|
||
* Open the provided image (`base.jpg`) as the background layer.
|
||
* **Effect 1**: Add a new layer. Use the Rectangle Select tool to select the region from **(0, 0) to (200, 200)** in the top-left corner, fill it with `#E74C3C`, and set this layer's opacity to **60%**.
|
||
* **Effect 2**: Add another new layer. Create an elliptical selection with a diameter of **150px** at the **center** of the image. Go to `Selection → Border`, set the border width to **3px**, and fill the resulting selection with `#FFFFFF`.
|
||
* Save the project as a `.xcf` file first. Then flatten all layers (Flatten Image) and export the result as `result.jpg`.
|
||
|
||
#### **A3. Color Correction and Layer Mask** `Normal`
|
||
|
||
Apply color correction and a layer mask to the provided image (`photo.jpg`) in GIMP.
|
||
|
||
* **Color correction**: Use the Hue-Saturation tool to increase Saturation by **+40**.
|
||
* **Curves adjustment**: Use the Curves tool to raise the output value of highlights by **+20 or more** and lower the output value of shadows by **-20 or less** to increase contrast.
|
||
* **Layer mask**: Add a layer mask to the image layer. Paste the provided mask image (`mask.png`) into the mask layer so that the background outside the subject is removed.
|
||
* Save the project as a `.xcf` file first. Then export the result as `result.png`, ensuring the transparent background is preserved (PNG format).
|
||
|
||
#### **A4. Text Effect and Glow** `Normal`
|
||
|
||
Apply a glow visual effect to text in GIMP.
|
||
|
||
* Canvas size: **600×200px**, Background color: `#1A1A2E`
|
||
* Add the text **"SPEED TEST"** in white (`#FFFFFF`), Bold, 60pt, centered on the canvas.
|
||
* Duplicate the text layer and place the duplicate **below** the original text layer.
|
||
* Rasterize the duplicate layer (Layer → Rasterize). In the Layers panel, set the duplicate layer's **Mode** to **`Color`** first. Then set the foreground color to `#4A90D9` and apply `Edit → Fill with Foreground Color` so the blue color is applied.
|
||
* Apply **Gaussian Blur** with a radius of **8px** to the duplicate layer to complete the outer glow effect.
|
||
* Save the project as a `.xcf` file first, then export the result as `result.png`.
|
||
|
||
#### **A5. Text Warp and Filter Effects** `Hard`
|
||
|
||
Rasterize text and apply a combination of Distorts filters in GIMP to create visual distortion effects.
|
||
|
||
* Canvas size: **800×300px**, Background color: `#0D0D0D`
|
||
* Add the text **"DISTORTION"** in white (`#FFFFFF`), Bold, 72pt, centered on the canvas.
|
||
* Rasterize the text layer (Layer → Rasterize).
|
||
* Apply the following two Distorts filters to the rasterized text layer in order:
|
||
* **Ripple**: Amplitude **8**, Wavelength **40**, Orientation: Horizontal
|
||
* **Whirl and Pinch**: Whirl angle **20** degrees, Pinch **0**, Radius **1.0**
|
||
* Duplicate the distorted text layer. Apply **Gaussian Blur** with a radius of **3px** to the duplicate, then place the duplicate **below** the original layer to create a ghosting effect.
|
||
* Save the project as a `.xcf` file first, then export the result as `result.png`.
|
||
|
||
---
|
||
|
||
### 📐 B. Layout — HTML / CSS Only
|
||
|
||
#### **B1. Flexbox Card Layout** `Easy`
|
||
|
||
Build a card list layout using HTML and CSS only.
|
||
|
||
* Use **Flexbox** to arrange cards **4 per row**. (Use `flex-wrap: wrap`)
|
||
* Each card must contain: an image area (`<div>`, height **160px**, with a background color), a title (`<h3>`), body text (`<p>`), and a button (`<button>`).
|
||
* Set the gap between cards to **24px**.
|
||
* Create **8** dummy cards. Each card's image area must have a different background color.
|
||
|
||
#### **B2. Sticky Header and CSS Interaction** `Easy`
|
||
|
||
Build a header with CSS-only interactions using HTML and CSS only.
|
||
|
||
* Use **Flexbox** to arrange the header with a logo text on the left, 4 menu links in the center, and 2 buttons on the right.
|
||
* Implement a CSS-only interaction where hovering over a menu link reveals a **2px solid underline** with a `transition` effect. (Use `border-bottom` or a `::after` pseudo-element)
|
||
* Use `position: sticky; top: 0;` to keep the header fixed at the top while scrolling.
|
||
* Place dummy content with a minimum height of **3000px** below the header to enable scrolling.
|
||
|
||
#### **B3. CSS Grid Two-Column Layout** `Normal`
|
||
|
||
Build a two-column page layout using CSS Grid with HTML and CSS only.
|
||
|
||
* Use **CSS Grid** to create a layout with a fixed left sidebar (**250px**) and a right main content area (`1fr`).
|
||
* Place a vertical navigation menu (5 items) in the left sidebar. Style the first item to indicate an active state using background color and text color.
|
||
* Place a title, body text, and a **3-column card grid** (using CSS Grid, 6 cards) in sequence in the right main area.
|
||
* Apply `height: 100vh` and `overflow: hidden` to the layout wrapper. Apply `height: 100vh` and `overflow-y: auto` to both the sidebar and the main area so that each scrolls independently.
|
||
|
||
#### **B4. CSS Animations and Transitions** `Normal`
|
||
|
||
Implement various CSS animations and transition effects on a single page using HTML and CSS only.
|
||
|
||
* **Card hover effect**: Arrange 4 cards in a row using Flexbox. On hover, each card should move up **8px** and have a stronger shadow using a transition. (Use `transform: translateY(-8px)`, `box-shadow`, and `transition`)
|
||
* **Loading spinner**: Use `@keyframes` to create a circular loading spinner. The spinner should be **48px** in diameter with a **4px** border. Only the top border (`border-top`) should be colored `#3498DB`; the remaining borders should be `#e0e0e0`. The spinner rotates infinitely.
|
||
* **Fade-in text**: On page load, a heading (`<h1>`) should animate from `translateY(20px)` to `translateY(0)` while fading from `opacity: 0` to `opacity: 1` using `@keyframes`. (`animation-duration: 0.8s`, `animation-fill-mode: both`)
|
||
|
||
#### **B5. CSS Grid and Advanced Selectors** `Hard`
|
||
|
||
Build a two-column content layout with a CSS-only tab interaction using HTML and CSS only.
|
||
|
||
* Use **CSS Grid** to create a two-column layout: left column (65%) and right column (35%).
|
||
* **Left area**: Place a main image area (`<div>`, height **300px**, with a background color) and a horizontal thumbnail list below it (Flexbox, `overflow-x: auto`). Include **5** thumbnails, each as an **80×80px** `<div>` with a different background color.
|
||
* **Right area — upper**: Place a title, subtitle, and body text.
|
||
* **Right area — lower**: Implement 3 tabs (Overview / Details / Reviews) with their respective content areas.
|
||
* Use `<input type="radio" name="tab">` and the CSS `:checked` selector **without JavaScript** so that only the selected tab's content is shown.
|
||
* Hide the `<input type="radio">` elements with `display: none` and style the corresponding `<label>` elements to look like tab buttons.
|
||
* At the bottom of the right area, place a Primary button and a Secondary button side by side. Apply a `@keyframes` animation to the Primary button so its background color transitions smoothly on hover.
|
||
|
||
---
|
||
|
||
### ⚡ C. Frontend — JavaScript (Vanilla)
|
||
|
||
#### **C1. localStorage-based Memo App** `Easy`
|
||
|
||
Build a simple memo app using Vanilla JavaScript and `localStorage`.
|
||
|
||
* The page must include a text input (`<textarea>`) and a **"Save"** button.
|
||
* Clicking **"Save"** adds the memo to a JSON array stored under the `memos` key in `localStorage`. Empty content must not be saved.
|
||
* Render the saved memo list below the input area. Each item must include the memo content and a **"Delete"** button.
|
||
* Clicking **"Delete"** removes the item from `localStorage` and immediately updates the rendered list.
|
||
* The memo list must persist after a page refresh.
|
||
|
||
#### **C2. DOM Manipulation and Event Handling** `Easy`
|
||
|
||
Build a dynamic list with item management using Vanilla JavaScript.
|
||
|
||
* The page must include a text input (`<input type="text">`) and an **"Add"** button.
|
||
* Clicking **"Add"** or pressing Enter adds the input value as a new list item. Empty values must not be added. The input field is cleared after adding.
|
||
* Each item must include a **"Done"** button and a **"Delete"** button.
|
||
* Clicking **"Done"** applies a strikethrough (`text-decoration: line-through`) to the item text. Clicking again toggles it back.
|
||
* Clicking **"Delete"** removes the item immediately.
|
||
* Display the current **total item count** and **completed item count** at the top of the list in real time.
|
||
|
||
#### **C3. Dynamic JSON Rendering and Filtering** `Normal`
|
||
|
||
Build a page that fetches a local JSON file and dynamically renders its content using Vanilla JavaScript and the Fetch API.
|
||
|
||
* Fetch the provided `posts.json` file at `/module_a/c3/posts.json` using the Fetch API. The file contains an array of objects with the keys `id`, `title`, `body`, and `category`. (20 items total)
|
||
* Display a **loading spinner** (CSS animation) while fetching, and remove it when the fetch completes.
|
||
* Render all items as cards. Each card must display `id`, `title`, `body`, and `category`.
|
||
* Implement a search input that filters cards in real time by `title`. (Case-insensitive; updates immediately on each keystroke)
|
||
* If the Fetch request fails, display the error message **"Failed to load data."** on the screen.
|
||
|
||
> **Provided file**: `posts.json` (pre-placed in the competitor's working directory at `/module_a/c3/`)
|
||
|
||
#### **C4. Form Validation** `Normal`
|
||
|
||
Implement real-time form validation using Vanilla JavaScript.
|
||
|
||
* Build a registration form with the following fields: **Name**, **Email**, **Password**, **Confirm Password**
|
||
* Validation rules for each field:
|
||
* **Name**: Required, at least 2 characters
|
||
* **Email**: Required, must contain both `@` and `.`
|
||
* **Password**: Required, at least 8 characters, must contain both letters and numbers
|
||
* **Confirm Password**: Required, must match the Password field exactly
|
||
* When focus leaves a field (`blur` event), display an error message below that field if the validation rule is not met. Remove the error message immediately once the condition is satisfied.
|
||
* The **"Submit"** button must only be enabled when all fields are valid. Clicking it should trigger `alert("Registration complete.")`.
|
||
|
||
#### **C5. IntersectionObserver and Deferred Rendering** `Hard`
|
||
|
||
Implement infinite scroll and deferred rendering using `IntersectionObserver`.
|
||
|
||
* Define an array of 50 dummy items in JavaScript. Each item must have `id`, `title`, and `color`. (`color` is an arbitrary hex color value in `#RRGGBB` format)
|
||
* On initial load, render **10** items. Each item consists of a **200px-tall** color `<div>` and its `title` text. The first 10 items should display their `color` value as the background color immediately.
|
||
* Place a **sentinel element** (`<div id="sentinel">`) at the bottom of the list. When the sentinel enters the viewport, automatically render the next 10 items.
|
||
* While new items are being added, insert a **loading spinner element** directly before the sentinel to display it, and remove it once rendering is complete. (Simulate async behavior using `setTimeout` with 600ms)
|
||
* Once all **50 items** have been rendered, stop observing the sentinel (`observer.unobserve`) and display the message **"All items loaded."**
|
||
* New items should be added to the DOM with their color `<div>` initially set to `#cccccc` (grey). Use a **separate `IntersectionObserver` instance** to watch each `<div>`, and replace the background color with the real color from the element's `data-color` attribute the moment it enters the viewport.
|
||
|
||
---
|
||
|
||
### 🛢️ D. Backend — PHP / MySQL
|
||
|
||
> **Provided files**: `posts_dump.sql` for D4 and `data_dump.sql` for D5 are pre-placed in each task's working directory.
|
||
|
||
#### **D1. PDO-based CRUD API** `Easy`
|
||
|
||
Build a REST API to handle data using PHP and MySQL (PDO).
|
||
|
||
* Create an `items` table using the structure below. Submit the table creation SQL as `table.sql`.
|
||
* `id` (INT, PK, AUTO_INCREMENT), `title` (VARCHAR 100, NOT NULL), `content` (TEXT), `created_at` (TIMESTAMP DEFAULT CURRENT_TIMESTAMP)
|
||
* Implement the following 4 endpoints in a single file `api.php`, branching by HTTP method (`$_SERVER['REQUEST_METHOD']`).
|
||
* `GET /module_a/d1/api.php` → Return all items as a JSON array
|
||
* `POST /module_a/d1/api.php` → Add a new item. Body (JSON): `{"title": "...", "content": "..."}`. Read the request body using `php://input` and parse it with `json_decode()`. On success, return the inserted row as JSON.
|
||
* `PUT /module_a/d1/api.php?id={id}` → Update `title` and `content`. Body (JSON): `{"title": "...", "content": "..."}`. Read the request body using `php://input` and parse it with `json_decode()`. On success, return the updated row as JSON.
|
||
* `DELETE /module_a/d1/api.php?id={id}` → Delete the item. On success, return `{"message": "deleted"}`.
|
||
* For PUT/DELETE requests with a non-existent `id`, return HTTP status **404** and `{"error": "Not found"}`.
|
||
* All responses must include a `Content-Type: application/json` header.
|
||
|
||
#### **D2. File Upload Handling** `Easy`
|
||
|
||
Implement image file upload functionality using PHP.
|
||
|
||
* In `upload.php`, implement both a file upload HTML form (GET) and the upload processing logic (POST). Set the form's `enctype` to `multipart/form-data`.
|
||
* Upload conditions: only **jpg, jpeg, png, gif** extensions are allowed; file size must be **2MB or less**.
|
||
* Save files that pass validation to the `uploads/` directory using the naming format **`{time()}_{original_filename}`**. If the `uploads/` directory does not exist, create it manually.
|
||
* On successful upload, display the uploaded image using an `<img>` tag.
|
||
* For validation failures, display the appropriate error message:
|
||
* Invalid extension: **"File type not allowed."**
|
||
* Size exceeded: **"File size exceeds 2MB."**
|
||
|
||
#### **D3. Session-based Authentication System** `Normal`
|
||
|
||
Implement user registration, login, and logout using PHP sessions and MySQL.
|
||
|
||
* `users` table: `id` (INT, PK, AUTO_INCREMENT), `email` (VARCHAR 100, UNIQUE), `password` (VARCHAR 255), `name` (VARCHAR 50), `created_at` (TIMESTAMP DEFAULT CURRENT_TIMESTAMP)
|
||
* **`register.php`**: On GET, render an HTML registration form. On POST, check for duplicate emails and save the password hashed with `password_hash()`, then redirect immediately to `login.php` without displaying a success message. If the email is already in use, display the error **"This email is already registered."** above the form.
|
||
* **`login.php`**: On GET, render an HTML login form. On POST, verify the password with `password_verify()`. On success, store `user_id`, `name`, and `email` in the session and redirect to `mypage.php`. On failure, display the error **"Incorrect email or password."** above the form.
|
||
* **`logout.php`**: On GET, destroy the session with `session_destroy()` and redirect to `login.php`.
|
||
* **`mypage.php`**: If `user_id` is not in the session, redirect to `login.php`. If logged in, display the message **"Welcome, {name}!"** and a logout link.
|
||
|
||
#### **D4. Pagination** `Normal`
|
||
|
||
Implement server-side pagination using PHP and MySQL (PDO).
|
||
|
||
* Create the `posts` table yourself, then import the provided SQL dump (`posts_dump.sql`). Table structure: `id`, `title`, `content`, `created_at`
|
||
* In `index.php`, display **10 posts per page**.
|
||
* The current page is determined by the URL query string `?page={n}`, defaulting to 1. Use `LIMIT` and `OFFSET` in your SQL query to retrieve only the data for the current page. If `page` is less than 1 or exceeds the total number of pages, redirect to page 1.
|
||
* Display pagination links at the bottom of the page. **Show all page numbers**, highlight the current page with an active style, and include Previous/Next buttons. Disable the Previous button on the first page and the Next button on the last page.
|
||
* Each post item must show the title and registration date. Clicking the title navigates to `view.php?id={id}`, which displays the full content of that post.
|
||
|
||
#### **D5. Multi-table Aggregate Queries** `Hard`
|
||
|
||
Implement a statistics API using multi-table JOINs and aggregate queries.
|
||
|
||
* Create all 3 tables yourself, then import the provided SQL dump (`data_dump.sql`). **Tables must be created in the order `users` → `posts` → `comments`** (to satisfy foreign key dependencies).
|
||
* `users` (`id`, `name`, `created_at`)
|
||
* `posts` (`id`, `user_id` — references users.id, `title`, `category`, `view_count`, `created_at`)
|
||
* `comments` (`id`, `post_id` — references posts.id, `created_at`)
|
||
* In a single file `stats.php`, branch on the `type` query string and return the following 3 responses:
|
||
* `GET /module_a/d5/stats.php?type=daily` → Return the number of new posts per day for the past 7 days, sorted by date ascending. Response format: `[{"date": "2026-04-06", "count": 5}, ...]`
|
||
* `GET /module_a/d5/stats.php?type=category` → Return the total post count and average view count per category, sorted by post count descending. Response format: `[{"category": "tech", "post_count": 12, "avg_views": 340}, ...]`
|
||
* `GET /module_a/d5/stats.php?type=top` → Return the top 5 posts by comment count, sorted by comment count descending. Response format: `[{"post_id": 3, "title": "...", "author": "Alice Johnson", "comment_count": 18}, ...]`
|
||
* All queries must use PDO **Prepared Statements**.
|
||
* For unknown `type` values, return HTTP **400** and `{"error": "Invalid type"}`.
|
||
|
||
---
|
||
|
||
## 4. Submission Guidelines
|
||
|
||
Upload all deliverables to the server directory `/module_a/`. Each task's output must be saved in a subdirectory named after the task ID. (e.g., `/module_a/a1/`, `/module_a/b1/`, `/module_a/c1/`, `/module_a/d1/`)
|
||
|
||
---
|
||
|
||
## 5. Marking Scheme — Total 25 Points
|
||
|
||
| Category | Task | Points |
|
||
| :--- | :--- | :---: |
|
||
| **A. Design** | A1. Layer Compositing and Text Placement | 0.5 |
|
||
| | A2. Selection and Layer Compositing | 0.5 |
|
||
| | A3. Color Correction and Layer Mask | 1.5 |
|
||
| | A4. Text Effect and Glow | 1.5 |
|
||
| | A5. Text Warp and Filter Effects | 2.0 |
|
||
| **B. Layout** | B1. Flexbox Card Layout | 0.5 |
|
||
| | B2. Sticky Header and CSS Interaction | 0.5 |
|
||
| | B3. CSS Grid Two-Column Layout | 1.5 |
|
||
| | B4. CSS Animations and Transitions | 1.5 |
|
||
| | B5. CSS Grid and Advanced Selectors | 2.0 |
|
||
| **C. Frontend** | C1. localStorage-based Memo App | 0.5 |
|
||
| | C2. DOM Manipulation and Event Handling | 0.5 |
|
||
| | C3. Dynamic JSON Rendering and Filtering | 1.5 |
|
||
| | C4. Form Validation | 1.5 |
|
||
| | C5. IntersectionObserver and Deferred Rendering | 2.0 |
|
||
| **D. Backend** | D1. PDO-based CRUD API | 0.5 |
|
||
| | D2. File Upload Handling | 0.5 |
|
||
| | D3. Session-based Authentication System | 1.5 |
|
||
| | D4. Pagination | 1.5 |
|
||
| | D5. Multi-table Aggregate Queries | 3.0 |
|
||
| **Total** | | **25** |
|