realease lần 1

This commit is contained in:
NAS\NASPC
2026-06-09 08:09:51 +07:00
parent ee6b2a8919
commit 9b6e188df8
19 changed files with 3638 additions and 3118 deletions
+58 -43
View File
@@ -186,67 +186,82 @@ To configure CI for your project, run the ci-cd sub-generator (`jhipster ci-cd`)
- [Node.js](https://nodejs.org/)
- [NPM](https://www.npmjs.com/)
## Deployment Guide (Ubuntu server + aaPanel + Docker)
## Deployment Guide (Ubuntu server + aaPanel + Executable JAR)
### 1. Build the WAR file (on Windows)
To deploy this application to an external Tomcat server, you need to build a `.war` file.
Run the following command on your Windows PC to build the production-ready `.war` file:
### 1. Build the JAR file (on Windows)
Spring Boot applications come with a built-in web server, meaning you can package your entire application into a single executable `.jar` file!
Run the following command on your Windows PC to build the production-ready `.jar` file:
```powershell
./gradlew -Pprod -Pwar clean bootWar
./gradlew -Pprod clean bootJar
```
Alternatively, you can use the built-in npm script:
```powershell
npm run java:war:prod
npm run java:jar:prod
```
The generated `.war` file will be located at `build/libs/sisvietnamvn-0.0.1-SNAPSHOT.war`.
The generated `.jar` file will be located at `build/libs/sisvietnamvn-1.0.3.jar`.
### 2. Standalone Tomcat 10 using Docker Compose (on Ubuntu Server)
Since this project uses Java 21, it requires **Tomcat 10.1**. You can set this up on your Ubuntu server using Docker Compose.
### 2. Deploy and Run the application (on Ubuntu Server)
Create a folder for Tomcat, and add a `docker-compose.yml` file:
```yaml
version: '3.8'
You have two main options to run the application on Ubuntu: using **aaPanel** or using standard **systemd**.
services:
tomcat:
image: tomcat:10.1-jdk21
container_name: my-tomcat-server
ports:
- "8080:8080"
environment:
- TZ=Asia/Ho_Chi_Minh
volumes:
- ./webapps:/usr/local/tomcat/webapps
- ./logs:/usr/local/tomcat/logs
restart: unless-stopped
```
#### Option A: Using aaPanel (Recommended)
If you are using aaPanel, it is highly recommended to use its built-in **Java Project** manager to easily run the JAR file.
Create the required folders and start the server:
```bash
mkdir webapps logs
docker compose up -d
```
1. **Install Java 21**: Make sure Java 21 is installed on your Ubuntu server via aaPanel.
2. **Upload the JAR**: Upload the `sisvietnamvn-1.0.3.jar` file to a folder on your server (e.g., `/www/wwwroot/sisvietnam.vn/`).
3. **Add Java Project**: In aaPanel, go to **Website** -> **Java Project** -> **Add Java project**.
4. **Configure Project**:
- **Project Type**: `Spring_boot`
- **Project jar**: Select the `sisvietnamvn-1.0.3.jar` file you just uploaded.
- **Project Port**: `8080` (or whichever port your app is configured to use).
- **Run user**: `www` (or `root`)
- Click **Submit**. aaPanel will automatically run your JAR file in the background!
### 3. Deploy the application
After running the build command successfully, your .war file is located here:
d:\website_sisvietnam_moi\website_sisvietnam.vn\sisvietnamvn\build\libs\sisvietnamvn-0.0.1-SNAPSHOT.war
(Inside your project folder, look for the build folder, then open the libs folder).
This sisvietnamvn-0.0.1-SNAPSHOT.war file is the one you need to upload to your Ubuntu server and rename to ROOT.war inside the Tomcat webapps folder.
#### Option B: Using systemd (Standard Ubuntu)
If you prefer managing the server manually via terminal, you can register the JAR as a `systemd` service so it runs continuously in the background and starts automatically on boot.
Upload the `.war` file you built in Step 1 to the server.
Place it inside the `webapps/` folder you just created and rename it to `ROOT.war` (so it serves on the root path `/`). Tomcat will automatically detect and deploy it.
1. **Upload the JAR**: Upload the `sisvietnamvn-1.0.3.jar` file to your server (e.g., `/var/www/sisvietnam/sisvietnamvn.jar`).
2. **Create the Service File**:
```bash
sudo nano /etc/systemd/system/sisvietnam.service
```
3. **Add Configuration**: Paste the following, making sure the `ExecStart` path matches where you put the jar.
```ini
[Unit]
Description=SIS Vietnam Spring Boot Application
After=syslog.target network.target
### 4. Configure NGINX Reverse Proxy (aaPanel)
Using NGINX (via aaPanel) with Tomcat is a highly recommended setup. NGINX acts as a Reverse Proxy, handling internet traffic (ports 80 and 443 for SSL) and forwarding it to Tomcat on port 8080.
[Service]
User=root
ExecStart=/usr/bin/java -jar /var/www/sisvietnam/sisvietnamvn.jar
SuccessExitStatus=143
Restart=always
RestartSec=10
[Install]
WantedBy=multi-user.target
```
4. **Start the Service**: Run the following commands to reload systemd, enable the service to start on boot, and start it now:
```bash
sudo systemctl daemon-reload
sudo systemctl enable sisvietnam.service
sudo systemctl start sisvietnam.service
```
5. **Check Status & Logs**:
- To check if it's running: `sudo systemctl status sisvietnam.service`
- To view the live terminal output: `sudo journalctl -u sisvietnam.service -f`
### 3. Configure NGINX Reverse Proxy (aaPanel)
When you set up a Java Project in aaPanel, it can automatically map your domain and set up a reverse proxy. If you need to verify or set it up manually:
1. **Add Site**: Go to aaPanel -> Website -> Add site. Enter your domain (e.g., `sisvietnam.vn`) and select **Static** for the PHP version.
2. **Setup Reverse Proxy**: Click "Settings" for the new site -> "Reverse proxy" -> "Add reverse proxy".
- **Proxy name**: `TomcatProxy`
2. **Setup Reverse Proxy**: Click "Settings" for the site -> "Reverse proxy" -> "Add reverse proxy".
- **Proxy name**: `JavaProxy`
- **Target URL**: `http://127.0.0.1:8080`
- **Sent Domain**: `$host`
3. **Spring Boot Proxy Trust**: Ensure Spring Boot knows it is behind a proxy so it can read client IPs and HTTP schemes correctly. In `src/main/resources/config/application-prod.yml`, verify this exists:
3. **Spring Boot Proxy Trust**: Ensure Spring Boot knows it is behind a proxy so it can read client IPs correctly. In `src/main/resources/config/application-prod.yml`, verify this exists:
```yaml
server:
forward-headers-strategy: framework
```
4. **SSL**: Use aaPanel's built-in SSL manager to apply Let's Encrypt certificates to your NGINX site. Your Tomcat backend won't need to worry about SSL certificates.
4. **SSL**: Use aaPanel's built-in SSL manager to apply Let's Encrypt certificates to your NGINX site. You don't need to configure SSL inside Spring Boot itself.
-49
View File
@@ -1,49 +0,0 @@
const fs = require('fs');
const aboutPath = 'd:\\website_sisvietnam_moi\\website_sisvietnam.vn\\sisvietnamvn\\src\\main\\resources\\templates\\about.html';
const layoutPath = 'd:\\website_sisvietnam_moi\\website_sisvietnam.vn\\sisvietnamvn\\src\\main\\resources\\templates\\fragments\\layout.html';
const footerPath = 'd:\\website_sisvietnam_moi\\website_sisvietnam.vn\\sisvietnamvn\\src\\main\\resources\\templates\\fragments\\footer.html';
let aboutHtml = fs.readFileSync(aboutPath, 'utf8');
let layoutHtml = fs.readFileSync(layoutPath, 'utf8');
// 1. Extract Head
const headMatch = aboutHtml.match(/<head>([\s\S]*?)<\/head>/i);
const headContent = headMatch ? headMatch[1] : '';
// 2. Extract Main Content (everything between </header> and <footer ...>)
const mainMatch = aboutHtml.match(/<\/header>([\s\S]*?)<footer/i);
let mainContent = mainMatch ? mainMatch[1] : '';
// The match stops at `<footer`, so it doesn't include it.
// 3. Extract Footer (from <footer id="l--main-footer"... to </footer>)
const footerMatch = aboutHtml.match(/(<footer id="l--main-footer"[\s\S]*?<\/footer>)/i);
let footerContent = footerMatch ? footerMatch[1] : '';
// Create footer.html with fragment
footerContent = footerContent.replace('<footer id="l--main-footer" class="site-footer">', '<footer id="l--main-footer" class="site-footer" th:fragment="footer">');
fs.writeFileSync(footerPath, footerContent, 'utf8');
// Update layout.html to include footer
layoutHtml = layoutHtml.replace(/<footer>[\s\S]*?<\/footer>/i, '<footer th:replace="~{fragments/footer :: footer}"></footer>');
fs.writeFileSync(layoutPath, layoutHtml, 'utf8');
// Construct new about.html
const newAboutHtml = `<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org" xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout" layout:decorate="~{fragments/layout}">
<head>
<th:block layout:fragment="head">
${headContent}
</th:block>
</head>
<body>
<div layout:fragment="content" class="dialog-off-canvas-main-canvas umass-platform-homepage path-node page-node-type-landing-page landing-page transparent-header" data-off-canvas-main-canvas="">
${mainContent}
</div>
</body>
</html>
`;
fs.writeFileSync(aboutPath, newAboutHtml, 'utf8');
console.log("Transformation completed successfully!");
+1 -1
View File
@@ -12,7 +12,7 @@ plugins {
}
group = "com.sisvietnamvn.web"
version = "1.0.1"
version = "1.0.4"
description = ""
+1
View File
@@ -0,0 +1 @@
./gradlew -Pprod clean bootJar
-14
View File
@@ -1,14 +0,0 @@
const fs = require('fs');
const path = 'd:\\website_sisvietnam_moi\\website_sisvietnam.vn\\sisvietnamvn\\src\\main\\resources\\templates\\about.html';
let content = fs.readFileSync(path, 'utf8');
// Replace UMass Undergraduate specific files with the ones from the main UMass Amherst folder if we want, or just leave it.
// But since the folder was created while bootRun is active, let's use a gradle command to process resources.
// However, fixing the image links is important.
content = content.replace(/\/sites\/default\/files\/styles\/[^"'\s]+/gi, '/images/LogoSIS.svg');
// Also fix manifest 404
content = content.replace(/<link rel="manifest" href="[^"]+">/gi, '');
fs.writeFileSync(path, content, 'utf8');
-5
View File
@@ -1,5 +0,0 @@
$path = 'd:\website_sisvietnam_moi\website_sisvietnam.vn\sisvietnamvn\src\main\resources\templates\about.html'
$content = Get-Content -Raw -Path $path -Encoding UTF8
$content = $content -replace '/sites/default/files/styles/[^"''\s]+', '/images/LogoSIS.svg'
$content = $content -replace '<link rel="manifest" href="[^"]+">', ''
Set-Content -Path $path -Value $content -Encoding UTF8
+18
View File
@@ -0,0 +1,18 @@
# This configuration is intended for development purpose, it's **your** responsibility to harden it for production
name: sisvietnam-oracle
services:
sisvietnam-oracle:
image: gvenzl/oracle-free:23-slim-faststart
environment:
# Password for the SYS and SYSTEM admin accounts
- ORACLE_PASSWORD=Oracle123!
# Name of the pluggable database
- ORACLE_DATABASE=sisvietnam
# Create an app user and password automatically
- APP_USER=sisvietnam
- APP_USER_PASSWORD=sisvietnam
ports:
- "1521:1521"
# Uncomment the following lines to make the database data persistent across restarts
# volumes:
# - ~/oracle-data:/opt/oracle/oradata
@@ -0,0 +1,24 @@
/* Custom CSS - This file has the highest priority and will override other styles */
[data-component-id="umass_base:tophat"] {
background-color: transparent !important;
}
/* Creates an overlay gradient on top of the ambient video */
.f--ambient-video {
position: relative;
}
.f--ambient-video::after {
content: "";
position: absolute;
bottom: 0;
left: 0;
width: 100%;
height: 50%;
/* Adjust how far up the gradient goes */
background-image: linear-gradient(180deg, transparent, #000);
pointer-events: none;
/* Allows clicks to pass through to the video controls */
z-index: 1;
/* Ensures it sits above the video */
}
@@ -37,14 +37,37 @@ img {
}
/* Colors */
.text-red { color: #881c1c; }
.text-black { color: #000; }
.text-grey { color: #666; }
.text-light { color: #fff; }
.bg-red { background: #881c1c; }
.bg-black { background: #111; }
.bg-grey { background: #f4f4f4; }
.bg-light-grey { background: #e5e5e5; }
.text-red {
color: #881c1c;
}
.text-black {
color: #000;
}
.text-grey {
color: #666;
}
.text-light {
color: #fff;
}
.bg-red {
background: #881c1c;
}
.bg-black {
background: #111;
}
.bg-grey {
background: #f4f4f4;
}
.bg-light-grey {
background: #e5e5e5;
}
/* Buttons */
.btn {
@@ -56,22 +79,27 @@ img {
transition: all 0.2s;
cursor: pointer;
}
.btn-red {
background: #881c1c;
color: #fff;
}
.btn-red:hover {
background: #6b1515;
}
.btn-red-outline {
border: 2px solid #881c1c;
color: #881c1c;
background: transparent;
}
.btn-red-outline:hover {
background: #881c1c;
color: #fff;
}
.btn-red-fill {
background: #881c1c;
color: #fff;
@@ -89,44 +117,58 @@ img {
/* ================= HEADER ================= */
.header {
position: absolute;
top: 0; left: 0; width: 100%;
top: 0;
left: 0;
width: 100%;
z-index: 100;
padding: 30px 0;
}
.header-inner {
display: flex;
justify-content: space-between;
align-items: flex-start;
}
.logo {
color: #fff;
line-height: 1.2;
}
.logo-light {
font-size: 16px;
font-weight: 400;
}
.logo-bold {
font-size: 24px;
font-weight: 800;
}
#umass_base:tophat {
background-color: transparent;
}
.nav-right {
display: flex;
flex-direction: column;
align-items: flex-end;
}
.top-nav {
display: flex;
align-items: center;
gap: 20px;
margin-bottom: 10px;
}
.top-nav a {
color: #fff;
font-size: 12px;
font-weight: 600;
text-transform: uppercase;
}
.search-btn {
background: #881c1c;
color: #fff;
@@ -138,10 +180,12 @@ img {
justify-content: center;
cursor: pointer;
}
.main-nav {
display: flex;
gap: 24px;
}
.main-nav a {
color: #fff;
font-size: 13px;
@@ -149,8 +193,13 @@ img {
text-transform: uppercase;
}
.mobile-only { display: none; }
.desktop-only { display: flex; }
.mobile-only {
display: none;
}
.desktop-only {
display: flex;
}
/* ================= HERO ================= */
.hero {
@@ -159,6 +208,7 @@ img {
min-height: 600px;
background: #000;
}
.hero-bg {
position: absolute;
top: 0;
@@ -168,26 +218,34 @@ img {
object-fit: cover;
z-index: 0;
}
.hero-overlay {
position: absolute;
top: 0; left: 0; width: 100%; height: 100%;
background: linear-gradient(to bottom, rgba(0,0,0,0.6) 0%, transparent 30%, transparent 60%, rgba(0,0,0,0.8) 100%);
top: 0;
left: 0;
width: 100%;
height: 100%;
background: linear-gradient(to bottom, rgba(0, 0, 0, 0.6) 0%, transparent 30%, transparent 60%, rgba(0, 0, 0, 0.8) 100%);
z-index: 1;
}
.hero-content {
position: absolute;
bottom: 60px;
left: 0; right: 0;
left: 0;
right: 0;
color: #fff;
padding-left: 20px;
z-index: 2;
}
.hero-title {
font-size: 56px;
font-weight: 800;
line-height: 1;
margin-bottom: 15px;
}
.hero-desc {
font-size: 16px;
font-weight: 400;
@@ -199,25 +257,30 @@ img {
padding: 60px 0;
background: #fff;
}
.step-into-inner {
align-items: center;
justify-content: flex-start;
gap: 80px;
}
.step-into-title {
font-size: 32px;
font-weight: 800;
line-height: 1.1;
}
.step-into-links {
display: flex;
gap: 80px;
}
.link-col {
display: flex;
flex-direction: column;
gap: 15px;
}
.link-col a {
font-weight: 700;
font-size: 14px;
@@ -229,6 +292,7 @@ img {
border-bottom: 1px solid #eee;
padding-bottom: 10px;
}
.link-col a .arrow {
color: #881c1c;
font-size: 10px;
@@ -239,13 +303,16 @@ img {
background: #111;
color: #fff;
}
.whats-life-inner {
align-items: stretch;
}
.whats-life-text {
flex: 1;
padding: 80px 60px 80px 0;
}
.tabs {
display: flex;
gap: 20px;
@@ -253,41 +320,50 @@ img {
border-bottom: 1px solid #333;
padding-bottom: 10px;
}
.tab {
font-size: 14px;
font-weight: 600;
color: #888;
}
.tab.active {
color: #fff;
border-bottom: 2px solid #fff;
padding-bottom: 11px;
margin-bottom: -12px;
}
.whats-life-text .subtitle {
font-size: 24px;
font-weight: 700;
margin-bottom: 15px;
}
.whats-life-text .desc {
font-size: 15px;
color: #ccc;
margin-bottom: 30px;
}
.whats-life-media {
flex: 1;
position: relative;
}
.whats-life-media img {
width: 100%;
height: 100%;
object-fit: cover;
}
.play-icon {
position: absolute;
top: 50%; left: 50%;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
width: 60px; height: 60px;
width: 60px;
height: 60px;
background: #881c1c;
color: #fff;
border: none;
@@ -297,7 +373,8 @@ img {
align-items: center;
justify-content: center;
cursor: pointer;
padding-left: 5px; /* center play triangle */
padding-left: 5px;
/* center play triangle */
}
/* ================= STATS ================= */
@@ -306,15 +383,18 @@ img {
background: #fff;
text-align: center;
}
.stats-inner {
justify-content: center;
gap: 100px;
}
.stat-number {
font-size: 48px;
font-weight: 800;
margin-bottom: 10px;
}
.stat-text {
font-size: 13px;
font-weight: 600;
@@ -326,32 +406,42 @@ img {
padding: 80px 0;
background: #fff;
}
.excellence-grid {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 40px;
}
.exc-col-left .desc {
font-size: 15px;
margin-bottom: 30px;
}
.mt-large { margin-top: 60px; }
.mt-large {
margin-top: 60px;
}
.story-card {
position: relative;
overflow: hidden;
}
.story-card img {
width: 100%;
display: block;
}
.story-card.has-bg .story-content {
position: absolute;
bottom: 0; left: 0; width: 100%;
background: linear-gradient(transparent, rgba(0,0,0,0.8));
bottom: 0;
left: 0;
width: 100%;
background: linear-gradient(transparent, rgba(0, 0, 0, 0.8));
padding: 40px 30px 20px;
color: #fff;
}
.tag {
display: block;
font-size: 11px;
@@ -359,19 +449,23 @@ img {
text-transform: uppercase;
margin-bottom: 8px;
}
.story-content h3 {
font-size: 22px;
font-weight: 700;
margin-bottom: 10px;
line-height: 1.2;
}
.story-content p {
font-size: 13px;
opacity: 0.9;
}
.story-card.large h3 {
font-size: 28px;
}
.sub-grid {
display: grid;
grid-template-columns: 1fr 1fr;
@@ -379,12 +473,44 @@ img {
gap: 0;
height: 300px;
}
.box-black { background: #000; padding: 30px; color: #fff; display: flex; flex-direction: column; justify-content: center; }
.box-red { background: #881c1c; padding: 30px; color: #fff; display: flex; flex-direction: column; justify-content: center; }
.box-grey { background: #444; padding: 30px; color: #fff; display: flex; flex-direction: column; justify-content: center; }
.play-box { background: #f4f4f4; display: flex; align-items: center; justify-content: center; }
.box-black {
background: #000;
padding: 30px;
color: #fff;
display: flex;
flex-direction: column;
justify-content: center;
}
.box-red {
background: #881c1c;
padding: 30px;
color: #fff;
display: flex;
flex-direction: column;
justify-content: center;
}
.box-grey {
background: #444;
padding: 30px;
color: #fff;
display: flex;
flex-direction: column;
justify-content: center;
}
.play-box {
background: #f4f4f4;
display: flex;
align-items: center;
justify-content: center;
}
.play-icon-large {
width: 80px; height: 80px;
width: 80px;
height: 80px;
background: #881c1c;
color: #fff;
border-radius: 50%;
@@ -399,25 +525,35 @@ img {
padding: 80px 0;
background: #fff;
}
.max-w-800 { max-width: 800px; margin-bottom: 40px; font-size: 15px; }
.max-w-800 {
max-width: 800px;
margin-bottom: 40px;
font-size: 15px;
}
.card-grid-3 {
display: grid;
grid-template-columns: repeat(3, 1fr);
gap: 30px;
}
.card-img img {
width: 100%;
aspect-ratio: 3/2;
object-fit: cover;
}
.card-body {
padding: 20px 0;
}
.card-body h3 {
font-size: 20px;
font-weight: 700;
margin-bottom: 10px;
}
.card-body p {
font-size: 14px;
color: #555;
@@ -428,28 +564,38 @@ img {
padding: 60px 0;
background: #fff;
}
.text-center { text-align: center; }
.text-center {
text-align: center;
}
.apply-buttons {
display: flex;
justify-content: center;
gap: 20px;
}
.apply-buttons .btn { width: 180px; }
.apply-buttons .btn {
width: 180px;
}
/* ================= I'M LOOKING FOR ================= */
.looking-for {
padding: 80px 0;
background: #e5e5e5;
}
.looking-grid {
display: flex;
gap: 60px;
}
.looking-grid .col {
flex: 1;
display: flex;
flex-direction: column;
}
.looking-grid .col a {
display: flex;
justify-content: space-between;
@@ -460,54 +606,70 @@ img {
font-size: 14px;
color: #000;
}
.looking-grid .col a .arrow { font-size: 10px; color: #881c1c; }
.looking-grid .col a .arrow {
font-size: 10px;
color: #881c1c;
}
/* ================= NEWS & EVENTS ================= */
.news-events {
padding: 80px 0;
background: #fff;
}
.news-layout {
display: grid;
grid-template-columns: 60% 35%;
gap: 5%;
}
.news-main img {
width: 100%;
aspect-ratio: 16/9;
object-fit: cover;
}
.mt-small { margin-top: 20px; }
.mt-small {
margin-top: 20px;
}
.news-main-title {
font-size: 28px;
font-weight: 800;
margin: 10px 0;
line-height: 1.2;
}
.news-main-desc {
font-size: 15px;
color: #444;
}
.news-sidebar {
display: flex;
flex-direction: column;
gap: 30px;
}
.news-small {
display: flex;
gap: 15px;
}
.news-small img {
width: 140px;
height: 100px;
object-fit: cover;
}
.news-info h4 {
font-size: 15px;
font-weight: 700;
margin: 5px 0;
line-height: 1.3;
}
.news-info p {
font-size: 12px;
color: #666;
@@ -519,17 +681,31 @@ img {
gap: 20px;
margin-top: 60px;
}
.event-card img {
width: 100%;
aspect-ratio: 3/2;
object-fit: cover;
}
.event-info {
padding: 15px 0;
}
.event-info .date { font-weight: 800; font-size: 12px; }
.event-info h4 { font-size: 16px; font-weight: 700; margin: 5px 0; }
.event-info .type { font-size: 13px; }
.event-info .date {
font-weight: 800;
font-size: 12px;
}
.event-info h4 {
font-size: 16px;
font-weight: 700;
margin: 5px 0;
}
.event-info .type {
font-size: 13px;
}
/* ================= FOOTER ================= */
.footer {
@@ -537,22 +713,27 @@ img {
color: #fff;
padding: 60px 0 30px;
}
.footer-top {
display: flex;
justify-content: space-between;
margin-bottom: 40px;
}
.footer-brand {
flex: 1;
}
.footer-logo {
display: flex;
align-items: center;
gap: 15px;
margin-bottom: 20px;
}
.m-box {
width: 50px; height: 50px;
width: 50px;
height: 50px;
background: #881c1c;
color: #fff;
font-size: 24px;
@@ -561,17 +742,21 @@ img {
align-items: center;
justify-content: center;
}
.footer-logo .logo-text {
font-size: 14px;
line-height: 1.2;
}
.social-icons {
display: flex;
gap: 15px;
}
.social-icons a {
color: #fff;
width: 20px; height: 20px;
width: 20px;
height: 20px;
}
.footer-links-grid {
@@ -580,18 +765,21 @@ img {
justify-content: flex-end;
gap: 80px;
}
.footer-col h4 {
font-size: 12px;
font-weight: 700;
color: #888;
margin-bottom: 15px;
}
.footer-col a {
display: block;
font-size: 13px;
color: #ccc;
margin-bottom: 10px;
}
.footer-bottom {
display: flex;
justify-content: space-between;
@@ -600,103 +788,214 @@ img {
font-size: 12px;
color: #888;
}
.bottom-links {
display: flex;
gap: 20px;
}
.bottom-links a { color: #888; }
.bottom-links a {
color: #888;
}
/* ================= RESPONSIVE TABLET (768px) ================= */
@media (max-width: 1024px) {
.desktop-only { display: none; }
.mobile-only { display: flex; }
.desktop-only {
display: none;
}
.mobile-only {
display: flex;
}
.header {
background: #111;
padding: 15px 0;
}
.mobile-actions {
display: flex;
align-items: center;
}
.search-btn-mobile {
background: #881c1c;
color: #fff;
border: none;
width: 40px; height: 40px;
width: 40px;
height: 40px;
display: flex;
align-items: center;
justify-content: center;
}
.hamburger-btn {
background: transparent;
color: #fff;
border: none;
width: 40px; height: 40px;
width: 40px;
height: 40px;
display: flex;
align-items: center;
justify-content: center;
}
.hero {
height: 100vh;
min-height: 500px;
padding: 0;
}
.hero-title { font-size: 40px; }
.hero-title {
font-size: 40px;
}
.step-into-inner {
flex-direction: column;
align-items: flex-start;
gap: 30px;
}
.step-into-links { width: 100%; justify-content: space-between; gap: 20px; }
.link-col { width: 48%; }
.link-col a { width: 100%; }
.whats-life-inner { flex-direction: column; }
.whats-life-text { padding: 40px 0; }
.whats-life-media { height: 400px; width: 100%; }
.stats-inner { gap: 30px; }
.excellence-grid { grid-template-columns: 1fr; }
.sub-grid { height: auto; grid-template-columns: 1fr 1fr; }
.card-grid-3 { grid-template-columns: 1fr; }
.card-img img { aspect-ratio: 16/9; }
.apply-buttons { flex-wrap: wrap; }
.news-layout { grid-template-columns: 1fr; gap: 40px; }
.events-grid { grid-template-columns: 1fr 1fr; }
.footer-top { flex-direction: column; gap: 40px; }
.footer-links-grid { justify-content: space-between; gap: 20px; }
.step-into-links {
width: 100%;
justify-content: space-between;
gap: 20px;
}
.link-col {
width: 48%;
}
.link-col a {
width: 100%;
}
.whats-life-inner {
flex-direction: column;
}
.whats-life-text {
padding: 40px 0;
}
.whats-life-media {
height: 400px;
width: 100%;
}
.stats-inner {
gap: 30px;
}
.excellence-grid {
grid-template-columns: 1fr;
}
.sub-grid {
height: auto;
grid-template-columns: 1fr 1fr;
}
.card-grid-3 {
grid-template-columns: 1fr;
}
.card-img img {
aspect-ratio: 16/9;
}
.apply-buttons {
flex-wrap: wrap;
}
.news-layout {
grid-template-columns: 1fr;
gap: 40px;
}
.events-grid {
grid-template-columns: 1fr 1fr;
}
.footer-top {
flex-direction: column;
gap: 40px;
}
.footer-links-grid {
justify-content: space-between;
gap: 20px;
}
}
/* ================= RESPONSIVE MOBILE (390px) ================= */
@media (max-width: 767px) {
.hero-title { font-size: 32px; }
.hero-desc { font-size: 14px; }
.step-into-links { flex-direction: column; }
.link-col { width: 100%; }
.stats-inner { flex-direction: column; gap: 40px; }
.sub-grid { grid-template-columns: 1fr; }
.apply-buttons { flex-direction: column; align-items: center; }
.apply-buttons .btn { width: 100%; max-width: 300px; }
.looking-grid { flex-direction: column; gap: 0; }
.events-grid { grid-template-columns: 1fr; }
.news-small { flex-direction: column; }
.news-small img { width: 100%; height: auto; aspect-ratio: 16/9; }
.footer-links-grid { flex-direction: column; }
.footer-bottom { flex-direction: column; gap: 20px; align-items: flex-start; }
.bottom-links { flex-direction: column; gap: 10px; }
}
.hero-title {
font-size: 32px;
}
.hero-desc {
font-size: 14px;
}
.step-into-links {
flex-direction: column;
}
.link-col {
width: 100%;
}
.stats-inner {
flex-direction: column;
gap: 40px;
}
.sub-grid {
grid-template-columns: 1fr;
}
.apply-buttons {
flex-direction: column;
align-items: center;
}
.apply-buttons .btn {
width: 100%;
max-width: 300px;
}
.looking-grid {
flex-direction: column;
gap: 0;
}
.events-grid {
grid-template-columns: 1fr;
}
.news-small {
flex-direction: column;
}
.news-small img {
width: 100%;
height: auto;
aspect-ratio: 16/9;
}
.footer-links-grid {
flex-direction: column;
}
.footer-bottom {
flex-direction: column;
gap: 20px;
align-items: flex-start;
}
.bottom-links {
flex-direction: column;
gap: 10px;
}
}
@@ -1456,7 +1456,8 @@ html[data-whatintent="mouse"] [data-component-id="umass_base:button"] {
}
}
.transparent-header, body:has(.transparent-header) {
.transparent-header,
body:has(.transparent-header) {
[data-component-id="umass_base:site-header"] {
#mobile-hamburger {
@@ -13860,4 +13861,4 @@ html[data-whatinput=mouse] .lity .lity-wrap .lity-container .lity-close:focus {
.claro-autocomplete__message {
color: var(--gin-color-primary);
}
}
@@ -19,7 +19,7 @@
<a class="logo" href="#" aria-label="SIS">
<div>
<img src="https://cdn.victorphan.net/sisvietnamvn/LogoSIS%20(1).png" alt="Logo SIS" />
<img src="https://sisvietnam.vn/wp-content/uploads/2026/06/LogoSIS-1.png" alt="Logo SIS" />
</div>
</a>
File diff suppressed because it is too large Load Diff
@@ -1,18 +1,25 @@
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org" xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>UMass Amherst</title>
<!-- Google Fonts: Lora and Open Sans -->
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link href="https://fonts.googleapis.com/css2?family=Lora:ital,wght@0,400..700;1,400..700&family=Open+Sans:ital,wght@0,300..800;1,300..800&display=swap" rel="stylesheet">
<link
href="https://fonts.googleapis.com/css2?family=Lora:ital,wght@0,400..700;1,400..700&family=Open+Sans:ital,wght@0,300..800;1,300..800&display=swap"
rel="stylesheet">
<!-- Add base CSS here if available -->
<th:block layout:fragment="head"></th:block>
<!-- Custom CSS (Highest Priority) -->
<link rel="stylesheet" th:href="@{/css/custom.css}">
</head>
<body>
<header th:replace="~{fragments/header :: #l--main-header}"></header>
@@ -22,4 +29,5 @@
<footer th:replace="~{fragments/footer :: footer}"></footer>
</body>
</html>
</html>
File diff suppressed because it is too large Load Diff
@@ -1,4 +1,4 @@
<!DOCTYPE html>
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org" xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout"
layout:decorate="~{fragments/layout}">
@@ -43,10 +43,6 @@
class="block block-system block-system-main-block tc--template-container tc--chaptered-page">
<div class="t--template t--chaptered-page">
<style>
/* Fix tall SIS logo overlapping the page title specifically for this page */
@media (min-width:768px) and (max-width: 1024px) {
@@ -54,6 +50,23 @@
padding-top: 100px !important;
}
}
/* Override tophat background specifically for the Lien He page */
[data-component-id="umass_base:tophat"] {
background-color: var(--color-black) !important;
}
[data-component-id="umass_base:site-header"] {
& .primary-navigation {
& .m--main-menu {
&>li {
a {
color: var(--color-white);
}
}
}
}
}
</style>
<div class="content-top">
<div class="lc--layout-container lc--two-column">
@@ -111,16 +124,14 @@
<div class="f--field f--link">
<a class="link"
href="#">
<a class="link" href="#">
Số Điện Thoại Thường Dùng
</a>
</div>
<div class="f--field f--link">
<a class="link"
href="#">
<a class="link" href="#">
Các Khoa Phòng Thường Liên Hệ
</a>
</div>
@@ -873,8 +884,7 @@
<div class="f--field f--button">
<a href="#"
class="button-text-link button-context-light "
<a href="#" class="button-text-link button-context-light "
aria-label="Xem chi tiết About University Health Services"
target="_self" data-component-id="umass_base:button">
@@ -922,8 +932,7 @@
<div class="f--field f--button">
<a href="#"
class="button-text-link button-context-light "
<a href="#" class="button-text-link button-context-light "
aria-label="Xem chi tiết About Office of Equity and Inclusion"
target="_self" data-component-id="umass_base:button">
@@ -979,8 +988,7 @@
<div class="f--field f--button">
<a href="#"
class="button-text-link button-context-light "
<a href="#" class="button-text-link button-context-light "
aria-label="Xem chi tiết About Title IX Coordinator" target="_self"
data-component-id="umass_base:button">
@@ -1025,8 +1033,7 @@
<path
d="M56 14H8c-1.1 0-2 0.9-2 2v32c0 1.1 0.9 2 2 2h48c1.1 0 2-0.9 2-2V16C58 14.9 57.1 14 56 14zM50.5 18L32 33.4 13.5 18H50.5zM10 46V20.3l20.7 17.3C31.1 37.8 31.5 38 32 38s0.9-0.2 1.3-0.5L54 20.3V46H10z">
</path>
</svg></a><br>413-545-2121<br><a
href="#">Anonymous
</svg></a><br>413-545-2121<br><a href="#">Anonymous
witness form</a></p>
</div>
@@ -1038,8 +1045,7 @@
<div class="f--field f--button">
<a href="#"
class="button-text-link button-context-light "
<a href="#" class="button-text-link button-context-light "
aria-label="Xem chi tiết About UMass Amherst Police Department"
target="_self" data-component-id="umass_base:button">
@@ -1112,4 +1118,4 @@
</main>
</body>
</html>
</html>
-30
View File
@@ -1,30 +0,0 @@
$aboutFile = 'd:\website_sisvietnam_moi\website_sisvietnam.vn\sisvietnamvn\src\main\resources\templates\about.html'
$indexFile = 'd:\website_sisvietnam_moi\website_sisvietnam.vn\sisvietnamvn\src\main\resources\templates\index.html'
$aboutContent = Get-Content -Raw -Path $aboutFile -Encoding UTF8
$indexContent = Get-Content -Raw -Path $indexFile -Encoding UTF8
# Replace Header
$aboutContent = $aboutContent -replace '(?s)<header id="l--main-header">.*?</header>', '<header th:replace="~{fragments/header :: header}"></header>'
# Extract Footer from index.html
$indexFooter = [regex]::Match($indexContent, '(?s)<footer id="l--main-footer".*?</footer>').Value
# Replace Footer in about.html
$aboutContent = $aboutContent -replace '(?s)<footer id="l--main-footer".*?</footer>', $indexFooter
# Apply extra CSS from index.html
$cssSnippet = @"
<link href="https://fonts.googleapis.com/css2?family=Open+Sans:ital,wght@0,300..800;1,300..800&display=swap" rel="stylesheet">
<style>
body, h1, h2, h3, h4, h5, h6, p, a, div, li, button, input, textarea, span:not([class*="fa"]):not([class*="icon"]):not([class*="material"]) {
font-family: 'Open Sans', Arial, Helvetica, sans-serif !important;
}
#block-footerbranding img {
filter: drop-shadow(0px 0px 10px rgba(255, 255, 255, 0.9)) drop-shadow(0px 0px 20px rgba(255, 255, 255, 0.7));
transition: filter 0.3s ease, transform 0.3s ease;
}
"@
$aboutContent = $aboutContent -replace '<style>', ($cssSnippet + "`n <style>")
Set-Content -Path $aboutFile -Value $aboutContent -Encoding UTF8
-25
View File
@@ -1,25 +0,0 @@
const fs = require('fs');
const path = 'd:\\website_sisvietnam_moi\\website_sisvietnam.vn\\sisvietnamvn\\src\\main\\resources\\templates\\about.html';
let content = fs.readFileSync(path, 'utf8');
const replacements = [
{ regex: /<h1>\s*Undergraduate\s*<\/h1>/gi, replacement: '<h1>Giới thiệu Bệnh viện S.I.S Cần Thơ</h1>' },
{ regex: /<span class="nolink">Get Your Degree<\/span>/gi, replacement: '<span class="nolink">Danh mục Giới thiệu</span>' },
{ regex: /Be Bright\. Be Bold\. Be You\./g, replacement: 'Sứ mệnh & Tầm nhìn' },
{ regex: /UMass Amherst is where possibilities happen.*?<\/p>/gi, replacement: 'S.I.S Cần Thơ là bệnh viện chuyên sâu về Cấp cứu Đột quỵ và Tim mạch. Sứ mệnh của chúng tôi là "Cứu Tinh Cho Bệnh Nhân Đột Quỵ". Chúng tôi tự hào mang đến các dịch vụ chăm sóc y tế chuẩn quốc tế cho người dân Đồng bằng sông Cửu Long và cả nước.</p>' },
{ regex: /Explore Every Possibility/g, replacement: 'Cơ sở vật chất hiện đại' },
{ regex: /Over 100 bachelor.*?degree programs\./gi, replacement: 'Hệ thống phòng mổ HYBRID, máy DSA, MRI 3 Tesla, CT 128 lát cắt hiện đại bậc nhất khu vực.' },
{ regex: /Small Classes Big Ideas/g, replacement: 'Đội ngũ Chuyên gia' },
{ regex: /Commonwealth Honors College.*?community/gi, replacement: 'Tập hợp các Giáo sư, Bác sĩ và Chuyên gia đầu ngành về Đột quỵ, Tim mạch và Thần kinh.' },
{ regex: /Beyond the Classroom/g, replacement: 'Chăm sóc toàn diện' },
{ regex: /Take what you learn and put it.*?abroad\./gi, replacement: 'Quy trình cấp cứu tối ưu, rút ngắn "thời gian vàng" kết hợp các dịch vụ tầm soát và phục hồi chức năng.' },
{ regex: /Empowering Your Best/g, replacement: 'Thành tựu nổi bật' },
{ regex: /Keep Exploring/g, replacement: 'Tìm hiểu thêm' }
];
replacements.forEach(r => {
content = content.replace(r.regex, r.replacement);
});
fs.writeFileSync(path, content, 'utf8');
-23
View File
@@ -1,23 +0,0 @@
$file = 'd:\website_sisvietnam_moi\website_sisvietnam.vn\sisvietnamvn\src\main\resources\templates\about.html'
$content = Get-Content -Raw -Path $file -Encoding UTF8
$replacements = @{
'<h1>\s*Undergraduate\s*</h1>' = '<h1>Giới thiệu Bệnh viện S.I.S Cần Thơ</h1>'
'<span class="nolink">Get Your Degree</span>' = '<span class="nolink">Danh mục Giới thiệu</span>'
'Be Bright\. Be Bold\. Be You\.' = 'Sứ mệnh & Tầm nhìn'
'UMass Amherst is where possibilities happen.*?</p>' = 'S.I.S Cần Thơ là bệnh viện chuyên sâu về Cấp cứu Đột quỵ và Tim mạch. Sứ mệnh của chúng tôi là "Cứu Tinh Cho Bệnh Nhân Đột Quỵ". Chúng tôi tự hào mang đến các dịch vụ chăm sóc y tế chuẩn quốc tế cho người dân Đồng bằng sông Cửu Long và cả nước.</p>'
'Explore Every Possibility' = 'Cơ sở vật chất hiện đại'
'Over 100 bachelor.*?degree programs\.' = 'Hệ thống phòng mổ HYBRID, máy DSA, MRI 3 Tesla, CT 128 lát cắt hiện đại bậc nhất khu vực.'
'Small Classes Big Ideas' = 'Đội ngũ Chuyên gia'
'Commonwealth Honors College.*?community' = 'Tập hợp các Giáo sư, Bác sĩ và Chuyên gia đầu ngành về Đột quỵ, Tim mạch và Thần kinh.'
'Beyond the Classroom' = 'Chăm sóc toàn diện'
'Take what you learn and put it.*?abroad\.' = 'Quy trình cấp cứu tối ưu, rút ngắn "thời gian vàng" kết hợp các dịch vụ tầm soát và phục hồi chức năng.'
'Empowering Your Best' = 'Thành tựu nổi bật'
'Keep Exploring' = 'Tìm hiểu thêm'
}
foreach ($key in $replacements.Keys) {
$content = [regex]::Replace($content, $key, $replacements[$key], 'IgnoreCase,Singleline')
}
Set-Content -Path $file -Value $content -Encoding UTF8
-9
View File
@@ -1,9 +0,0 @@
const fs = require('fs');
const indexPath = 'd:\\website_sisvietnam_moi\\website_sisvietnam.vn\\sisvietnamvn\\src\\main\\resources\\templates\\index.html';
let indexHtml = fs.readFileSync(indexPath, 'utf8');
indexHtml = indexHtml.replace(/<footer id="l--main-footer"[\s\S]*?<\/footer>/i, '<footer th:replace="~{fragments/footer :: footer}"></footer>');
fs.writeFileSync(indexPath, indexHtml, 'utf8');
console.log("Replaced footer in index.html");